Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 鵝廠系列一 : 仿QQ側滑菜單

鵝廠系列一 : 仿QQ側滑菜單

編輯:關於Android編程

好了,跟隨潮流,還是先看下效果,不然可能都沒人想看下去了(不會看到效果後不想看了吧O(∩_∩)O~)

效果vcHL19S2qNLlv9i8/rXEyrG68iy7ubvhvfjQ0NbY0MKy4sG/us2yvL7WLjwvcD4NCjxwPjxpbWcgYWx0PQ=="重寫前" src="/uploadfile/Collfiles/20160624/20160624092943692.png" title="\" />

嗯,就是讓左面板在主面板的下面,所以我們自定義的控件SlideLayout繼承FrameLayout.一般自定義控件會涉及到三個方法,onMeasure 測量,onLayout布局,onDraw繪制,如果是繼承ViewGroup的話我們一般需要重寫布局方法,繼承view的話要重寫onDraw方法,當然你喜歡的話,都可以重寫.我們這裡繼承的是FrameLayout,它繼承的是ViewGroup,即它已經實現了布局方法,但是我說過了,我們要重新測量和布局,主要是對左面板slideView的測量和布局,我們要調整它的寬度和位置,所以我們先來重寫onMeasure方法.onMeasure(int widthMeasureSpec, int heightMeasureSpec)看這兩個參數,看名字就知道中式英語很像有沒有,測量說明書,即我們能通過測量說明書來進行測量,其實這兩個數是測量說明書上給定的規范值,所以叫測量規范,結合我們下面的代碼進行講解

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        mSlideView = getChildAt(0);
        mMainView = getChildAt(1);

        mWd_width = MeasureSpec.getSize(widthMeasureSpec);     //使用測量說明書得到我們想要知道的寬度和高度值,它是match_parent的,所以拿到的就是窗體的寬高度
        mWd_height = MeasureSpec.getSize(heightMeasureSpec);

        int sl_widthSpec = MeasureSpec.makeMeasureSpec((mWd_width /3) * 2,MeasureSpec.EXACTLY); //制作測量說明書規定值
        int sl_heightSpec = MeasureSpec.makeMeasureSpec(mWd_height,MeasureSpec.EXACTLY);
        mSlideView.measure(sl_widthSpec,sl_heightSpec);

        mMainView.measure(widthMeasureSpec,heightMeasureSpec);   //主面板和自己一樣,都是填充整個窗體,所以測量說明書的規定值一樣

        setMeasuredDimension(mWd_width, mWd_height);            //測量自己用這個方法.
    }

制作測量規范值的函數,第一個參數的意思是,你想要分配多少像素吧,沒有給解釋,這個不重要….重要的是第二個參數,第二個參數有三個值可以填,分別是UNSPECIFIED,EXACTLY,AT_MOST,我們看它們單詞的意思就好理解了,第一個說不確定,就叫它自己看著辦;第二個意思是精確的,即我們生活中確定以及肯定,它會禁它最大的努力去按第一個參數的值測量,你的值填的離譜,它也滿足不了.第三個就和第二個挺像了,盡量,就是比精確的肯定性差一點.所以我們如果自己確定一個測量規范的話,我確定我要側滑面板占屏幕的三分之二,所以我們這裡用EXACTLY.測量好了,接著就是我們的布局了

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mSl_width = mSlideView.getWidth();
        mSlideView.layout(mSlideView.getLeft() -  mSl_width /3 ,mSlideView.getTop(),mSlideView.getRight(),mSlideView.getBottom());
    }

布局的時候,說明測量已經完成了,所以我們是可以拿到我們左面板的寬度的,然後執行layout(l,t,r,b)看參數就明白了,這個不講了,我把它的left設為-自己看的三分之一,是自己為了做的像一點的想法.來看看我們重寫測量布局後的效果
布局後

好了,這時候開始我們的拖動了.視圖拖動我們用這個類ViewDragHelper,視圖拖動幫助者,首選看看怎麼得到它的對象static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb)它是個靜態方法,所以我們不是new出來的,第一個參數說拖動VIEW的父容器是誰,因為我們拖動的是主面板和左面板,所以他們的父容器是自己,即填this,第二個參數是靈敏度,它還有一個重載函數,只有兩個參數,這個參數沒寫默認是1.0f,所以我們一般也填1.0f.第三個參數是ViewDragHelper 的內部類Callback ,我們需要寫個類繼承它.

class MyDragCallBack extends ViewDragHelper.Callback {

        @Override  //表示要捕獲那個孩子,即處理哪個孩子的拖到事件,返回false不處理
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

這個是抽象方法,我們必須實現,因為兩個孩子都要拖動,所以直接返回true,要像橫向拖動,我們還得重寫它的一個方法

   @Override //
        public int clampViewPositionHorizontal(View child, int left, int dx) {  //left相對於屏幕左側的偏移值,是拖動的建議值

            if (child == mMainView){            //主面板拖動范圍
                if (left < 0){
                    left = 0;
                }else if (left > (mSl_width)){
                    left = mSl_width;
                }

            }else if (child == mSlideView){      //左面板拖動范圍
                if (left > 0){
                    left = 0;
                }else if (left < -mSl_width){
                    left = mSl_width;
                }

            }
            return left;
        }

它默認是返回0的,即拖動不了的,同理它還有縱向的,這裡我們不需要,所以不重新,到這裡還是拖不動的,因為,我們的ViewDragHelper拚什麼拖動呢,我們還要把touch事件傳遞給他,它才能決定該不該拖動

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {


        return mViewDragHelper.shouldInterceptTouchEvent(event);   //交給它去判斷該不該攔截
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        try {

            mViewDragHelper.processTouchEvent(event);    //有可能會出錯,所以try一下
        }catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }

好了,這裡就可以拖動了,但是,只是一個view拖動了,另一個並不跟著動啊,所以我們還要另外重寫一個函數,onViewPositionChanged看名字就知道了吧,拖動的view改變的時候調用,所以我們在這裡動態的layout就好了

@Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);

            if (changedView == mMainView){

                mSlideView.layout( left/3 - mSl_width/3,mSlideView.getTop(),left/3 - mSl_width/3+mSl_width,mSlideView.getBottom());

            }else if (changedView == mSlideView){

                mMainView.layout(mSl_width + left,mMainView.getTop(),mSl_width + left + mWd_width,mMainView.getBottom());
            }




            invalidate();
        }

還有,當松開手的時候,我們應該看看左面板滑出來多少了,然後根據值判斷是不是該關閉,即我們重寫onViewReleased這個方法

@Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {

            if (mMainView.getLeft() < mSl_width/2){

                closeMenu();
            }else {

                openMenu();

            }
        }

 @Override
    public void computeScroll() {  //不停計算,不停調用
        if (mViewDragHelper.continueSettling(true)){
            ViewCompat.postInvalidateOnAnimation(this);  //不停刷新直到停止滑動
        }
    }


    public void closeMenu() {

        mViewDragHelper.smoothSlideViewTo(mMainView,0,mMainView.getTop());
        ViewCompat.postInvalidateOnAnimation(this);  //兼容,刷新界面.
        mMenuIsOpen = false;

    }


    public void openMenu() {

        mViewDragHelper.smoothSlideViewTo(mMainView,mSl_width,mMainView.getTop());
        ViewCompat.postInvalidateOnAnimation(this);
        mSlideView.layout(0,mSlideView.getTop(),mSl_width,mSlideView.getBottom());
        mMenuIsOpen = true;
    }

在這裡介紹下smoothSlideViewTo(View child, int finalLeft, int finalTop)看參數就明白了是吧,但是實現它的效果我們要ViewCompat.postInvalidateOnAnimation(this)刷新界面,這是兼容國產各大rom的,還要重寫computeScroll()這個方法,continueSettling(true)返回true表示還沒滑玩,所以繼續刷新界面.好了到這裡我們就做的差不多了,來看看效果吧
這裡寫圖片描述

好吧,為什麼會生成倒的gif圖片我也不知道,因為總是大了,弄了好幾次,把握不到度啊,治療治療頸椎病吧.看到這應該發現問題了吧,當在ViewGroup和listView上左右滑時劃不動.看到這也許有人說了,去ononInterceptTouchEvent那設置滑動判斷然後返回true攔截事件,再去Listview 的onTouchEvent判斷返回false表示我不消費這個事件,我想說我都做了,而且log顯示進入了我的判斷攔截事件.還是不行,我都開始懷疑人生.懷疑我對touch事件的認知,就去網上看別人怎麼說,說的都是我認知的,都不能解決這個問題.真的而且36度的天啊,很熱,很沮喪的感覺.反正弄了將近一天,真的////各種自己重寫view攔截…全部沒用…所以沒原理沒方法真的基本做不出來..突然重寫了ViewDragHelper.callback的一個方法就好了,是的,就是這個方法,我真的無法解釋,我都無語了.因為我想我事件都交給ViewDragHelper處理了,點ViewDragHelper.shouldInterceptTouchEvent(event)進去看源碼,看不懂…看到一個變量名有drag什麼,所以試一試的心態重寫了這個方法的.真的不知道這是撒原理,平常我們不重寫這個不是也能拖動麼,所以很少重寫,以後我每次都重寫了

 @Override
        public int getViewHorizontalDragRange(View child) {
            // 返回拖拽的范圍
            return mSl_width/3 * 2;
        }

重寫後效果就能拖了,各控件的touch事件也正常,到這基本完了,最後來弄我們的拖動監聽吧

public interface OnSlideListener{
        /**
         * @param view    //觸摸的是主界面還是菜單界面
         * @param left  //滑動了多長,負數向左滑,正數向右
         * @param persent //滑動相對於總長度的百分比
         */
        void onSlide(View view,int left,float persent);
    }

    private OnSlideListener mOnSlideListener;
    public void setOnSlideListener(OnSlideListener listener){
        mOnSlideListener = listener;
    }

在 onViewPositionChanged中添加


            if (mOnSlideListener != null){
                mOnSlideListener.onSlide(changedView,left,left * 1.0f / mSl_width);
            }

嗯,這裡還是貼下完整代碼吧,等下資源審核通過後我把項目文件下載路徑放在最下面,感興趣的朋友可以去下載看看

import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

/**
 * Created by Root on 2016/6/20.
 */
public class SlideLayout extends FrameLayout{


    private View mSlideView;
    private View mMainView;
    private ViewDragHelper mViewDragHelper;
    private int mWd_width;
    private int mWd_height;
    private int mSl_width;
    private boolean mMenuIsOpen;


    public SlideLayout(Context context) {
        this(context,null);
    }

    public SlideLayout(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public SlideLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mViewDragHelper = ViewDragHelper.create(this,1.0f,new MyDragCallBack());


    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        mSlideView = getChildAt(0);
        mMainView = getChildAt(1);

        mWd_width = MeasureSpec.getSize(widthMeasureSpec);     //使用測量說明書得到我們想要知道的寬度和高度值
        mWd_height = MeasureSpec.getSize(heightMeasureSpec);

        int sl_widthSpec = MeasureSpec.makeMeasureSpec((mWd_width /3) * 2,MeasureSpec.EXACTLY); //制作測量說明書規定值
        int sl_heightSpec = MeasureSpec.makeMeasureSpec(mWd_height,MeasureSpec.EXACTLY);
        mSlideView.measure(sl_widthSpec,sl_heightSpec);

        mMainView.measure(widthMeasureSpec,heightMeasureSpec);   //主面板和自己一樣,都是填充整個窗體,所以測量說明書的規定值一樣

        setMeasuredDimension(mWd_width, mWd_height);            //測量自己用這個方法.
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mSl_width = mSlideView.getWidth();
        mSlideView.layout(mSlideView.getLeft() -  mSl_width /3 ,mSlideView.getTop(),mSlideView.getRight(),mSlideView.getBottom());
    }



    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {


        return mViewDragHelper.shouldInterceptTouchEvent(event);   //交給它去判斷該不該攔截
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        try {

            mViewDragHelper.processTouchEvent(event);    //有可能會出錯,所以try一下
        }catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }


    class MyDragCallBack extends ViewDragHelper.Callback {

        @Override  //表示要捕獲那個孩子,即處理哪個孩子的拖到事件,返回false不處理
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

        @Override //
        public int clampViewPositionHorizontal(View child, int left, int dx) {  //left相對於屏幕左側的偏移值,是拖動的建議值

            if (child == mMainView){            //主面板拖動范圍
                if (left < 0){
                    left = 0;
                }else if (left > (mSl_width)){
                    left = mSl_width;
                }

            }else if (child == mSlideView){      //左面板拖動范圍
                if (left > 0){
                    left = 0;
                }else if (left < -mSl_width){
                    left = mSl_width;
                }

            }
            return left;
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            // 返回拖拽的范圍
            return mSl_width/3 * 2;
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);

            if (changedView == mMainView){

                mSlideView.layout( left/3 - mSl_width/3,mSlideView.getTop(),left/3 - mSl_width/3+mSl_width,mSlideView.getBottom());

            }else if (changedView == mSlideView){

                mMainView.layout(mSl_width + left,mMainView.getTop(),mSl_width + left + mWd_width,mMainView.getBottom());
            }

            if (mOnSlideListener != null){
                mOnSlideListener.onSlide(changedView,left,left * 1.0f / mSl_width);
            }


            invalidate();
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {

            if (mMainView.getLeft() < mSl_width/2){

                closeMenu();
            }else {

                openMenu();

            }
        }

    }

    @Override
    public void computeScroll() {  //不停計算,不停調用
        if (mViewDragHelper.continueSettling(true)){
            ViewCompat.postInvalidateOnAnimation(this);  //不停刷新直到停止滑動
        }
    }


    public void closeMenu() {

        mViewDragHelper.smoothSlideViewTo(mMainView,0,mMainView.getTop());
        ViewCompat.postInvalidateOnAnimation(this);  //兼容,刷新界面.
        mMenuIsOpen = false;

    }


    public void openMenu() {

        mViewDragHelper.smoothSlideViewTo(mMainView,mSl_width,mMainView.getTop());
        ViewCompat.postInvalidateOnAnimation(this);
        mSlideView.layout(0,mSlideView.getTop(),mSl_width,mSlideView.getBottom());
        mMenuIsOpen = true;
    }


    public boolean isMenuIsOpen(){
        return mMenuIsOpen;
    }


    public interface OnSlideListener{
        /**
         * @param view    //觸摸的是主界面還是菜單界面
         * @param left  //滑動了多長,負數向左滑,正數向右
         * @param persent //滑動相對於總長度的百分比
         */
        void onSlide(View view,int left,float persent);
    }

    private OnSlideListener mOnSlideListener;
    public void setOnSlideListener(OnSlideListener listener){
        mOnSlideListener = listener;
    }


}

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved