自定義view——仿酷狗的側滑選單

醉墨重生發表於2018-02-26

這裡寫圖片描述

直接貼原始碼:註解內容裡面都有

public class SlidingMenu extends HorizontalScrollView {
    private final int mMenuWidth;
    private View mMenuView;
    private View mContentView;
    //GestureDetector處理快速滑動
    private GestureDetector mGestureDetector;
    // 7.手指快速滑動 - 選單是否開啟
    private boolean mMenuIsOpen = false;
    private boolean mIsIntercept = false;

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

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

    public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
        float rightMarign = ta.getDimension(R.styleable.SlidingMenu_menuRightMargin, dip2px(context, 50));
        //選單的寬度是螢幕的寬度-右邊的一部分距離(自定義屬性)
        mMenuWidth = (int) (getScreenWidth(context) - rightMarign);
        ta.recycle();
        mGestureDetector = new GestureDetector(context, mGestureListener);
    }

    private GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            //快速滑動
            Log.e("TAG", "velocityX—>" + velocityX);//向右滑動大於0,向左滑動小於0

            // Bug  判斷左右還是上下   只有左右快速滑動才切換
            if(Math.abs(velocityY)>Math.abs(velocityX)){
                return super.onFling(e1, e2, velocityX, velocityY);//預設返回的是false
            }
            if (mMenuIsOpen) {//選單是開啟的,且當向左滑動的時候關閉選單
                if (velocityX < 0) {
                    closeMenu();
                    return true;
                }
            } else {//選單是關閉的,且當向右滑動的時候開啟選單
                if (velocityX > 0) {
                    openMenu();
                    return true;
                }
            }
            return super.onFling(e1, e2, velocityX, velocityY);//預設返回的是false
        }
    };

    //1.寬度不對,指定寬高
    @Override
    protected void onFinishInflate() {
        //這個方法是佈局解析完畢
        super.onFinishInflate();
        //指定寬高
        ViewGroup container = (ViewGroup) getChildAt(0);//LinearLayout
        //1.內容的寬度是螢幕的寬度
        int childCount = container.getChildCount();
        if (childCount != 2) {
            throw new RuntimeException("只能放置兩個子View!");
        }
        mContentView = container.getChildAt(1);

        ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
        layoutParams.width = getScreenWidth(getContext());
        mContentView.setLayoutParams(layoutParams);
        //2.選單的寬度是螢幕的寬度-右邊的一部分距離(自定義屬性)
        mMenuView = container.getChildAt(0);
        ViewGroup.LayoutParams menuParams = mMenuView.getLayoutParams();
        menuParams.width = mMenuWidth;
        mMenuView.setLayoutParams(menuParams);
    }

    //4.處理右邊的縮放,左邊的縮放和透明度,需要獲取當前不斷變化的位置
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        //Log.e("TAG", "l -> " + l);// 變化是 mMenuWidth - 0
        float scale = 1f * l / mMenuWidth;//變化是1->0
        //右邊的縮放 最小0.7f,最大1f
        float rightScale = 0.7f + 0.3f * scale;
        //設定右邊的縮放,預設是中心點(ViewCompat相容)
        //設定中心點
        ViewCompat.setPivotX(mContentView, 0);
        ViewCompat.setPivotY(mContentView, mContentView.getMeasuredHeight() / 2);
        ViewCompat.setScaleX(mContentView, rightScale);
        ViewCompat.setScaleY(mContentView, rightScale);

        //左邊縮放和透明度變化
        //縮放是從0.7到1完全展開
        float leftScale = 0.7f + (1 - scale) * 0.3f;
        ViewCompat.setScaleX(mMenuView, leftScale);
        ViewCompat.setScaleY(mMenuView, leftScale);
        //透明度,半透明到完全透明
        float leftAlpha = 0.5f + (1 - scale) * 0.5f;
        ViewCompat.setAlpha(mMenuView, leftAlpha);
        // 最後一個效果 退出這個按鈕剛開始是在右邊,安裝我們目前的方式永遠都是在左邊
        // 設定平移,先看一個抽屜效果
        // ViewCompat.setTranslationX(mMenuView,l);
        // 平移 l*0.25f
        ViewCompat.setTranslationX(mMenuView, 0.25f * l);
    }

    //點選右邊內容部分關閉選單
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        mIsIntercept = false;
        if (mMenuIsOpen) {
            float currentX = ev.getX();
            if (currentX > mMenuWidth) {
                //關閉選單
                closeMenu();
                //消費當前事件,攔截子view的事件,但是此時會呼叫自己的onTouchEvent事件
                mIsIntercept = true;
                return true;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //2.預設應該是關閉
        scrollTo(mMenuWidth, 0);
    }

    //3.當抬起的時候二選一,要麼關閉要麼開啟
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mIsIntercept) {//當是攔截事件,消費當前事件
            return true;
        }
        if (mGestureDetector.onTouchEvent(ev)) {//當點選了快速滑動,則消費當前事件
            return true;
        }
        // 1. 獲取手指滑動的速率,當期大於一定值就認為是快速滑動 , GestureDetector(系統提供好的類)
        // 2. 處理事件攔截 + ViewGroup 事件分發的原始碼實踐
        //    當選單開啟的時候,手指觸控右邊內容部分需要關閉選單,還需要攔截事件(開啟情況下點選內容頁不會響應點選事件)

        if (ev.getAction() == MotionEvent.ACTION_UP) {
            int currentScrollX = getScrollX();
            if (currentScrollX > mMenuWidth / 2) {
                //關閉
                closeMenu();
            } else {
                //開啟
                openMenu();
            }
            //確保不會呼叫super.onTouchEvent(ev),否則會不起作用
            return true;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 開啟選單
     */
    private void openMenu() {
        //smoothScrollTo有動畫
        smoothScrollTo(0, 0);
        mMenuIsOpen = true;
    }

    /**
     * 關閉選單
     */
    private void closeMenu() {
        //smoothScrollTo有動畫
        smoothScrollTo(mMenuWidth, 0);
        mMenuIsOpen = false;
    }

    /**
     * Dip into pixels
     */
    private int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 獲得螢幕寬度
     */
    private int getScreenWidth(Context context) {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.widthPixels;
    }
}

在上面的基礎上實現QQ6.0側滑效果

這裡寫圖片描述

首先去掉縮放,其次新增陰影,縮放把之前程式碼直接註解掉就可以了

給內容佈局新增陰影:首先將內容佈局摳出來,然後套一個Relayout佈局新增內容和陰影,然後再設定回去

 //1.內容的寬度是螢幕的寬度
        int childCount = container.getChildCount();
        if (childCount != 2) {
            throw new RuntimeException("只能放置兩個子View!");
        }
        mContentView = container.getChildAt(1);
        ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
        //內容佈區域性分新增陰影
        //把內容佈局提取出來
        container.removeView(mContentView);
        //在外面新增一層陰影
        RelativeLayout contentContainer = new RelativeLayout(getContext());
        contentContainer.addView(mContentView);
        mShadowView = new View(getContext());
        mShadowView.setBackgroundColor(Color.parseColor("#55000000"));
        contentContainer.addView(mShadowView);
        //最後把容器放回原來的位置
        layoutParams.width = getScreenWidth(getContext());
        contentContainer.setLayoutParams(layoutParams);
        container.addView(contentContainer);
        mShadowView.setAlpha(0);

onScrollChanged方法中動態設定透明度

 float scale = 1f * l / mMenuWidth;//變化是1->0
 float alpha=1-scale;
 mShadowView.setAlpha(alpha);
 ViewCompat.setTranslationX(mMenuView, 0.6f * l);

相關文章