仿QQ6.0主頁面?zhèn)然Ч?/h2>

1.概述


最近一直都在帶實習(xí)生做項目,發(fā)現(xiàn)自己好久沒有寫博客了,這幾天更新會比較頻繁,今天玩QQ的時候發(fā)現(xiàn)QQ主頁菜單滑動效果早就變了,實在忍不住晚上就來實現(xiàn)一下了!
  

  
這里寫圖片描述

2.實現(xiàn)


2.1. 實現(xiàn)的方式多種多樣
  2.1.1 自定義ViewGroup ,處理其onTouch事件
  2.1.2 FrameLayout + 手勢處理類GestureDetector
  2.2.3 使用Google自帶的DrawerLayout 對其進(jìn)行修改
  2.2.4 繼承自水平滾動HorizontalScrollView ***
大家可以看一下這個Android自定義ViewGroup打造各種風(fēng)格的SlidingMenu,這種方式繼承自ViewGroup,個人覺得還行但是還是比較繁瑣要處理的東西也比較多,那么我就用最后一種
2.2.4
*的方式實現(xiàn),有人說直接去網(wǎng)上下載一個源代碼就可以了,這我就只能GG了。

2.3. 自定義SlidingMenu extends HorizontalScrollView 然后寫好布局文件這個和ScrollView的用法一樣,只不過是橫向滾動的

/**
 * description:
 *      仿QQ6.0主頁面?zhèn)然淖远╒iew
 * Created by 曾輝 on 2016/11/1.
 * QQ:240336124
 * Email: 240336124@qq.com
 * 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. 運(yùn)行起來之后發(fā)現(xiàn)布局不對,完全亂了明明都是match_parent可是就是不行那么我們就需要用代碼指定菜單和內(nèi)容的寬度:
菜單的寬度 = 屏幕的寬度 - 自定義的右邊留出的寬度
內(nèi)容的寬度 = 屏幕的寬度

/**
 * description:
 *      仿QQ6.0主頁面?zhèn)然淖远╒iew
 * Created by 曾輝 on 2016/11/1.
 * QQ:240336124
 * Email: 240336124@qq.com
 * 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 轉(zhuǎn)成像素
     */
    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(主頁內(nèi)容布局)
            throw new IllegalStateException("SlidingMenu 根布局LinearLayout下面只允許兩個布局,菜單布局和主頁內(nèi)容布局");
        }

        // 2.獲取菜單和內(nèi)容布局
        mMenuView = container.getChildAt(0);
        mContentView = container.getChildAt(1);

        // 3.指定內(nèi)容和菜單布局的寬度
        // 3.1 菜單的寬度 = 屏幕的寬度 - 自定義的右邊留出的寬度
        mMenuView.getLayoutParams().width = mMenuWidth;
        // 3.2 內(nèi)容的寬度 = 屏幕的寬度
        mContentView.getLayoutParams().width = getScreenWidth();
    }

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

目前的效果就是可以滑動,并且菜單和主頁面內(nèi)容的布局寬度正常

這里寫圖片描述

2.5 接下來一開始就讓菜單滑動到關(guān)閉狀態(tài),手指滑動抬起判斷菜單打開和關(guān)閉并做相應(yīng)的處理 onLayout() onTouch() smoothScrollTo(),當(dāng)手指快速的時候切換菜單的狀態(tài)利用GestureDetector 手勢處理類:

    /**
 * description:
 * 仿QQ6.0主頁面?zhèn)然淖远╒iew
 * Created by 曾輝 on 2016/11/1.
 * QQ:240336124
 * Email: 240336124@qq.com
 * 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 轉(zhuǎn)成像素
     */
    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(主頁內(nèi)容布局)
            throw new IllegalStateException("SlidingMenu 根布局LinearLayout下面只允許兩個布局,菜單布局和主頁內(nèi)容布局");
        }

        // 2.獲取菜單和內(nèi)容布局
        mMenuView = container.getChildAt(0);
        mContentView = container.getChildAt(1);

        // 3.指定內(nèi)容和菜單布局的寬度
        // 3.1 菜單的寬度 = 屏幕的寬度 - 自定義的右邊留出的寬度
        mMenuView.getLayoutParams().width = mMenuWidth;
        // 3.2 內(nèi)容的寬度 = 屏幕的寬度
        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) {
                    // 關(guān)閉菜單
                    closeMenu();
                } else {
                    // 打開菜單
                    openMenu();
                }
                return false;
        }
        return super.onTouchEvent(ev);
    }

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

    /**
     * 關(guān)閉菜單
     */
    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);
        // 布局指定后會從新擺放子布局,當(dāng)其擺放完畢后,讓菜單滾動到不可見狀態(tài)
        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) {
            // 當(dāng)手指快速滑動時候回調(diào)的方法
            Log.e("TAG",velocityX+"");
            // 如果菜單打開 并且是向左快速滑動 切換菜單的狀態(tài)
            if(mMenuIsOpen){
                if(velocityX<-500){
                    toggleMenu();
                    return true;
                }
            }else{
                // 如果菜單關(guān)閉 并且是向右快速滑動 切換菜單的狀態(tài)
                if(velocityX>500){
                    toggleMenu();
                    return true;
                }
            }

            return false;
        }
    }

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

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

這里寫圖片描述

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

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

        // l 是 當(dāng)前滾動的x距離  在滾動的時候會不斷反復(fù)的回調(diào)這個方法
        Log.e(TAG,l+"");

        mMenuView.setTranslationX(l*0.8f);
    }

2.7. 實現(xiàn)菜單右邊菜單的陰影透明度效果,這個打算在主頁面內(nèi)容布局上面加一層陰影,用ImageView即可,那么我們的內(nèi)容View就需要換了

/**
 * description:
 * 仿QQ6.0主頁面?zhèn)然淖远╒iew
 * Created by 曾輝 on 2016/11/1.
 * QQ:240336124
 * Email: 240336124@qq.com
 * Version:1.0
 */
public class SlidingMenu extends HorizontalScrollView {
    private static final String TAG = "HorizontalScrollView";
    private Context mContext;

    // 4.給菜單和內(nèi)容View指定寬高 - 左邊菜單View
    private View mMenuView;

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

    // 7(4). 主頁面內(nèi)容View的布局包括陰影ImageView
    private ViewGroup mContentView;
    // 7.給內(nèi)容添加陰影效果 - 陰影的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 轉(zhuǎn)成像素
     */
    private float dip2px(int dip) {
        return TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
    }


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

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

        // 4.2.2.獲取菜單和內(nèi)容布局
        mMenuView = container.getChildAt(0);

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

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

        // 7.3 把原來的內(nèi)容View 和 陰影加到我們新創(chuàng)建的內(nèi)容布局中
        mContentView.addView(oldContentView);
        // 7.3.1 創(chuàng)建陰影ImageView
        mShadowIv = new ImageView(mContext);
        mShadowIv.setBackgroundColor(Color.parseColor("#99000000"));
        mContentView.addView(mShadowIv);

        // 7.4 把包含陰影的新的內(nèi)容View 添加到 LinearLayout中
        container.addView(mContentView);

        // 4.2.3.指定內(nèi)容和菜單布局的寬度
        // 4.2.3.1 菜單的寬度 = 屏幕的寬度 - 自定義的右邊留出的寬度
        mMenuView.getLayoutParams().width = mMenuWidth;
        // 4.2.3.2 內(nèi)容的寬度 = 屏幕的寬度
        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 關(guān)閉菜單
                    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 是 當(dāng)前滾動的x距離  在滾動的時候會不斷反復(fù)的回調(diào)這個方法
        Log.e(TAG,l+"");
        // 6. 實現(xiàn)菜單左邊抽屜樣式的動畫效果
        mMenuView.setTranslationX(l*0.8f);

        // 7.給內(nèi)容添加陰影效果 - 計算梯度值
        float gradientValue = l * 1f / mMenuWidth;// 這是  1 - 0 變化的值

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

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

    /**
     * 5.1.1 關(guān)閉菜單
     */
    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);
        // 布局指定后會從新擺放子布局,當(dāng)其擺放完畢后,讓菜單滾動到不可見狀態(tài)
        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) {
            // 當(dāng)手指快速滑動時候回調(diào)的方法
            Log.e(TAG,velocityX+"");
            // 5.3.1 如果菜單打開 并且是向左快速滑動 切換菜單的狀態(tài)
            if(mMenuIsOpen){
                if(velocityX<0){
                    toggleMenu();
                    return true;
                }
            }else{
                // 5.3.2 如果菜單關(guān)閉 并且是向右快速滑動 切換菜單的狀態(tài)
                if(velocityX>0){
                    toggleMenu();
                    return true;
                }
            }
            return false;
        }
    }

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

我們來看一下最后的效果吧,最終代碼量并不是很多。

這里寫圖片描述

附視頻地址:http://pan.baidu.com/s/1dEA0NLZ

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,094評論 25 709
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,399評論 4 61
  • 《安妮日記》的后記,是藏匿于密室里的8個人被逮捕,最后除了安妮父親——弗蘭克先生在二戰(zhàn)中存活了下來。我就像是看完了...
    卞鱻閱讀 969評論 0 1
  • 三歲零一個月。 暑假,跟奶奶一起住在姑姑家避暑,每天在小區(qū)里野,不亦樂乎,很快就跟一群大孩子玩熟了。奶奶每天還是照...
    不離不染閱讀 218評論 4 0
  • 有天晚上也做了一個夢,夢見我在舞臺上表演唱歌,歌曲名叫《簡書》
    Wendy_ing閱讀 250評論 0 0

友情鏈接更多精彩內(nèi)容