1. 程式人生 > >仿QQ6.0主頁面側滑效果

仿QQ6.0主頁面側滑效果

1.概述

  最近一直都在帶實習生做專案,發現自己好久沒有寫部落格了,這幾天更新會比較頻繁,今天玩QQ的時候發現QQ主頁選單滑動效果早就變了,實在忍不住晚上就來實現一下了! 這裡我已經寫得很詳細瞭如果大家還是看不太懂請看視訊講解:http://pan.baidu.com/s/1eS1bdLK
  
  這裡寫圖片描述

2.實現 

  2.1. 實現的方式多種多樣
  2.1.1 自定義ViewGroup ,處理其onTouch事件
  2.1.2 FrameLayout + 手勢處理類GestureDetector
  2.2.3 使用Google自帶的DrawerLayout 對其進行修改
  2.2.4 繼承自水平滾動HorizontalScrollView


大家可以看一下這個http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0909/6612.html,這種方式繼承自ViewGroup,個人覺得還行但是還是比較繁瑣要處理的東西也比較多,那麼我就用最後一種2.2.4的方式實現,有人說直接去網上下載一個原始碼就可以了,這我就只能GG了。

  2.3. 自定義SlidingMenu extends HorizontalScrollView 然後寫好佈局檔案這個和ScrollView的用法一樣,只不過是橫向滾動的

/**
 * description:
 *      仿QQ6.0主頁面側滑的自定View
 * Created by 曾輝 on 2016/11/1.
 * QQ:240336124
 * Email: 
[email protected]
* Version:1.0 */
public class SlidingMenu extends HorizontalScrollView { public SlidingMenu(Context context) { super(context); } public SlidingMenu(Context context, AttributeSet attrs) { super(context, attrs); } public SlidingMenu(Context context, AttributeSet attrs, int
defStyleAttr) { super(context, attrs, defStyleAttr); } }

2.4. 執行起來之後發現佈局不對,完全亂了明明都是match_parent可是就是不行那麼我們就需要用程式碼指定選單和內容的寬度:
選單的寬度 = 螢幕的寬度 - 自定義的右邊留出的寬度
內容的寬度 = 螢幕的寬度

/**
 * description:
 *      仿QQ6.0主頁面側滑的自定View
 * Created by 曾輝 on 2016/11/1.
 * QQ:240336124
 * Email: [email protected]
 * Version:1.0
 */
public class SlidingMenu extends HorizontalScrollView {
    private View mMenuView;
    private View mContentView;
    private int mMenuWidth;

    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 array = context.obtainStyledAttributes(attrs,R.styleable.SlidingMenu);
        float rightPadding = array.getDimension(
                R.styleable.SlidingMenu_rightPadding,dip2px(50));
        // 計算選單的寬度 = 螢幕的寬度 - 自定義右邊留出的寬度
        mMenuWidth = (int) (getScreenWidth() - rightPadding);
        array.recycle();
    }

    /**
     * 把dip 轉成畫素
     */
    private float dip2px(int dip) {
        return TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,dip,getResources().getDisplayMetrics());
    }


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        // 1.獲取根View也就是外層的LinearLayout
        ViewGroup container = (ViewGroup) this.getChildAt(0);

        int containerChildCount = container.getChildCount();
        if(containerChildCount>2){
            // 裡面只允許放置兩個佈局  一個是Menu(選單佈局) 一個是Content(主頁內容佈局)
            throw new IllegalStateException("SlidingMenu 根佈局LinearLayout下面只允許兩個佈局,選單佈局和主頁內容佈局");
        }

        // 2.獲取選單和內容佈局
        mMenuView = container.getChildAt(0);
        mContentView = container.getChildAt(1);

        // 3.指定內容和選單佈局的寬度
        // 3.1 選單的寬度 = 螢幕的寬度 - 自定義的右邊留出的寬度
        mMenuView.getLayoutParams().width = mMenuWidth;
        // 3.2 內容的寬度 = 螢幕的寬度
        mContentView.getLayoutParams().width = getScreenWidth();
    }

    /**
     * 獲取螢幕的寬度
     */
    public int getScreenWidth() {
        Resources resources = this.getResources();
        DisplayMetrics dm = resources.getDisplayMetrics();
        return dm.widthPixels;
    }
}

目前的效果就是可以滑動,並且選單和主頁面內容的佈局寬度正常

這裡寫圖片描述

  2.5 接下來一開始就讓選單滑動到關閉狀態,手指滑動擡起判斷選單開啟和關閉並做相應的處理 onLayout() onTouch() smoothScrollTo(),當手指快速的時候切換選單的狀態利用GestureDetector 手勢處理類:

    /**
 * description:
 * 仿QQ6.0主頁面側滑的自定View
 * Created by 曾輝 on 2016/11/1.
 * QQ:240336124
 * Email: [email protected]
 * Version:1.0
 */
public class SlidingMenu extends HorizontalScrollView {
    private View mMenuView;
    private View mContentView;
    private int mMenuWidth;
    // 手勢處理類 主要用來處理手勢快速滑動
    private GestureDetector mGestureDetector;

    // 選單是否開啟
    private boolean mMenuIsOpen = 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 array = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
        float rightPadding = array.getDimension(
                R.styleable.SlidingMenu_rightPadding, dip2px(50));
        // 計算選單的寬度 = 螢幕的寬度 - 自定義右邊留出的寬度
        mMenuWidth = (int) (getScreenWidth() - rightPadding);
        array.recycle();

        // 例項化手勢處理類
        mGestureDetector = new GestureDetector(context,new GestureListener());
    }

    /**
     * 把dip 轉成畫素
     */
    private float dip2px(int dip) {
        return TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
    }


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        // 1.獲取根View也就是外層的LinearLayout
        ViewGroup container = (ViewGroup) this.getChildAt(0);

        int containerChildCount = container.getChildCount();
        if (containerChildCount > 2) {
            // 裡面只允許放置兩個佈局  一個是Menu(選單佈局) 一個是Content(主頁內容佈局)
            throw new IllegalStateException("SlidingMenu 根佈局LinearLayout下面只允許兩個佈局,選單佈局和主頁內容佈局");
        }

        // 2.獲取選單和內容佈局
        mMenuView = container.getChildAt(0);
        mContentView = container.getChildAt(1);

        // 3.指定內容和選單佈局的寬度
        // 3.1 選單的寬度 = 螢幕的寬度 - 自定義的右邊留出的寬度
        mMenuView.getLayoutParams().width = mMenuWidth;
        // 3.2 內容的寬度 = 螢幕的寬度
        mContentView.getLayoutParams().width = getScreenWidth();
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        // 處理手指快速滑動
        if(mGestureDetector.onTouchEvent(ev)){
            return mGestureDetector.onTouchEvent(ev);
        }

        switch (ev.getAction()) {
            case MotionEvent.ACTION_UP:
                // 手指擡起獲取滾動的位置
                int currentScrollX = getScrollX();
                if (currentScrollX > mMenuWidth / 2) {
                    // 關閉選單
                    closeMenu();
                } else {
                    // 開啟選單
                    openMenu();
                }
                return false;
        }
        return super.onTouchEvent(ev);
    }

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

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

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        // 佈局指定後會從新擺放子佈局,當其擺放完畢後,讓選單滾動到不可見狀態
        if (changed) {
            scrollTo(mMenuWidth, 0);
        }
    }

    /**
     * 獲取螢幕的寬度
     */
    public int getScreenWidth() {
        Resources resources = this.getResources();
        DisplayMetrics dm = resources.getDisplayMetrics();
        return dm.widthPixels;
    }


    private class GestureListener extends GestureDetector.SimpleOnGestureListener{
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            // 當手指快速滑動時候回撥的方法
            Log.e("TAG",velocityX+"");
            // 如果選單開啟 並且是向左快速滑動 切換選單的狀態
            if(mMenuIsOpen){
                if(velocityX<-500){
                    toggleMenu();
                    return true;
                }
            }else{
                // 如果選單關閉 並且是向右快速滑動 切換選單的狀態
                if(velocityX>500){
                    toggleMenu();
                    return true;
                }
            }

            return false;
        }
    }

    /**
     * 切換選單的狀態
     */
    private void toggleMenu() {
        if(mMenuIsOpen){
            closeMenu();
        }else{
            openMenu();
        }
    }
}

到了這一步之後我們就可以切換選單了,並且處理了手指快速滑動,迫不及待的看下效果

這裡寫圖片描述

2.6. 實現選單左邊抽屜樣式的動畫效果,監聽滾動回撥的方法onScrollChanged() 這個就非常簡單了一句話就搞定,效果就不看了一起看終極效果吧

@Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

        // l 是 當前滾動的x距離  在滾動的時候會不斷反覆的回撥這個方法
        Log.e(TAG,l+"");

        mMenuView.setTranslationX(l*0.8f);
    }

2.7. 實現選單右邊選單的陰影透明度效果,這個打算在主頁面內容佈局上面加一層陰影,用ImageView即可,那麼我們的內容View就需要換了

/**
 * description:
 * 仿QQ6.0主頁面側滑的自定View
 * Created by 曾輝 on 2016/11/1.
 * QQ:240336124
 * Email: [email protected]
 * Version:1.0
 */
public class SlidingMenu extends HorizontalScrollView {
    private static final String TAG = "HorizontalScrollView";
    private Context mContext;

    // 4.給選單和內容View指定寬高 - 左邊選單View
    private View mMenuView;

    // 4.給選單和內容View指定寬高 - 選單的寬度
    private int mMenuWidth;
    // 5.3 手勢處理類 主要用來處理手勢快速滑動
    private GestureDetector mGestureDetector;
    // 5.3 選單是否開啟
    private boolean mMenuIsOpen = false;

    // 7(4). 主頁面內容View的佈局包括陰影ImageView
    private ViewGroup mContentView;
    // 7.給內容新增陰影效果 - 陰影的ImageView
    private ImageView mShadowIv;

    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);
        //4.1 計算左邊選單的寬度
        //4.1.1 獲取自定義的右邊留出的寬度
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
        float rightPadding = array.getDimension(
                R.styleable.SlidingMenu_rightPadding, dip2px(50));
        // 4.1.2 計算選單的寬度 = 螢幕的寬度 - 自定義右邊留出的寬度
        mMenuWidth = (int) (getScreenWidth() - rightPadding);
        array.recycle();

        // 5.3 例項化手勢處理類
        mGestureDetector = new GestureDetector(context,new GestureListener());

        this.mContext = context;
    }

    /**
     * 把dip 轉成畫素
     */
    private float dip2px(int dip) {
        return TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
    }


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        // 4.2 指定選單和內容View的寬度
        // 4.2.1.獲取根View也就是外層的LinearLayout
        ViewGroup container = (ViewGroup) this.getChildAt(0);

        int containerChildCount = container.getChildCount();
        if (containerChildCount > 2) {
            // 裡面只允許放置兩個佈局  一個是Menu(選單佈局) 一個是Content(主頁內容佈局)
            throw new IllegalStateException("SlidingMenu 根佈局LinearLayout下面只允許兩個佈局,選單佈局和主頁內容佈局");
        }

        // 4.2.2.獲取選單和內容佈局
        mMenuView = container.getChildAt(0);

        // 7.給內容新增陰影效果
        // 7.1 先new一個主內容佈局用來放  陰影和LinearLayout原來的內容佈局
        mContentView = new FrameLayout(mContext);
        ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);

        // 7.2 獲取原來的內容佈局,並把原來的內容佈局從LinearLayout中異常
        View oldContentView = container.getChildAt(1);
        container.removeView(oldContentView);

        // 7.3 把原來的內容View 和 陰影加到我們新建立的內容佈局中
        mContentView.addView(oldContentView);
        // 7.3.1 建立陰影ImageView
        mShadowIv = new ImageView(mContext);
        mShadowIv.setBackgroundColor(Color.parseColor("#99000000"));
        mContentView.addView(mShadowIv);

        // 7.4 把包含陰影的新的內容View 新增到 LinearLayout中
        container.addView(mContentView);

        // 4.2.3.指定內容和選單佈局的寬度
        // 4.2.3.1 選單的寬度 = 螢幕的寬度 - 自定義的右邊留出的寬度
        mMenuView.getLayoutParams().width = mMenuWidth;
        // 4.2.3.2 內容的寬度 = 螢幕的寬度
        mContentView.getLayoutParams().width = getScreenWidth();
    }

    /**
     * 5.處理手指擡起和快速滑動切換選單
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // 5.3 處理手指快速滑動
        if(mGestureDetector.onTouchEvent(ev)){
            return mGestureDetector.onTouchEvent(ev);
        }

        switch (ev.getAction()) {
            case MotionEvent.ACTION_UP:
                // 5.1 手指擡起獲取滾動的位置
                int currentScrollX = getScrollX();
                if (currentScrollX > mMenuWidth / 2) {
                    // 5.1.1 關閉選單
                    closeMenu();
                } else {
                    // 5.1.2 開啟選單
                    openMenu();
                }
                return false;
        }
        return super.onTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

        // l 是 當前滾動的x距離  在滾動的時候會不斷反覆的回撥這個方法
        Log.e(TAG,l+"");
        // 6. 實現選單左邊抽屜樣式的動畫效果
        mMenuView.setTranslationX(l*0.8f);

        // 7.給內容新增陰影效果 - 計算梯度值
        float gradientValue = l * 1f / mMenuWidth;// 這是  1 - 0 變化的值

        // 7.給內容新增陰影效果 - 給陰影的View指定透明度   0 - 1 變化的值
        float shadowAlpha = 1 - gradientValue;
        mShadowIv.setAlpha(shadowAlpha);
    }

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

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

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        // 佈局指定後會從新擺放子佈局,當其擺放完畢後,讓選單滾動到不可見狀態
        if (changed) {
            scrollTo(mMenuWidth, 0);
        }
    }

    /**
     * 獲取螢幕的寬度
     */
    public int getScreenWidth() {
        Resources resources = this.getResources();
        DisplayMetrics dm = resources.getDisplayMetrics();
        return dm.widthPixels;
    }


    /**
     * 5.3 處理手指快速滑動
     */
    private class GestureListener extends GestureDetector.SimpleOnGestureListener{
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            // 當手指快速滑動時候回撥的方法
            Log.e(TAG,velocityX+"");
            // 5.3.1 如果選單開啟 並且是向左快速滑動 切換選單的狀態
            if(mMenuIsOpen){
                if(velocityX<0){
                    toggleMenu();
                    return true;
                }
            }else{
                // 5.3.2 如果選單關閉 並且是向右快速滑動 切換選單的狀態
                if(velocityX>0){
                    toggleMenu();
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * 切換選單的狀態
     */
    private void toggleMenu() {
        if(mMenuIsOpen){
            closeMenu();
        }else{
            openMenu();
        }
    }
}

我們來看一下最後的效果吧,最終程式碼量不是很多啦,需要的請下載原始碼,如果是實現QQ5.0或是酷狗的側滑效果可以自己改改,也可以加我QQ或是留言給我

這裡寫圖片描述