1. 程式人生 > >仿QQ側滑選單

仿QQ側滑選單

效果圖

這裡寫圖片描述

這裡寫圖片描述

自定義控制元件


/**
 * 自定義側滑控制元件
 * Created by xiaoyehai on 2016/11/29.
 */
public class SlidingMenu extends FrameLayout {

    private View menuView; //選單view

    private View mainView; //主介面view

    /**
     * ViewDragHelper:它主要用於處理ViewGroup中對子View的拖拽處理,
     * 本質是對觸控事件的解析類;
     */
    private ViewDragHelper viewDragHelper;

    private
int width; private float dragRange; //拖拽範圍 // private FloatEvaluator floatEvaluator; //float計算器 // private IntEvaluator intEvaluator; //int計算器 //定義狀態常量:列舉 enum DragState { Open, Close; } //當前SlidingMenu的狀態,預設關閉 private DragState currentState = DragState.Close; public
SlidingMenu(Context context) { this(context, null); } public SlidingMenu(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SlidingMenu(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } /** * 獲取SlidingMenu的狀態 * * @return
*/
public DragState getCurrentState() { return currentState; } private void init() { viewDragHelper = ViewDragHelper.create(this, callback); // floatEvaluator = new FloatEvaluator(); // intEvaluator = new IntEvaluator(); } @Override protected void onFinishInflate() { super.onFinishInflate(); //簡單的異常處理,該控制元件只能有2個佈局 if (getChildCount() != 2) { throw new IllegalArgumentException("SlidingMenu only have 2 children!"); } menuView = getChildAt(0); mainView = getChildAt(1); } /** * 該方法在onMeasure()執行完後執行,可以在該方法初始化自己和子View的寬高 * * @param w * @param h * @param oldw * @param oldh */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width = getMeasuredWidth(); dragRange = width * 0.6f; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // 讓ViewDragHelper幫我們判斷是否應該攔截 return viewDragHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { // 將觸控事件交給ViewDragHelper來解析處理 viewDragHelper.processTouchEvent(event); return true; } /** * 回撥類 */ private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() { /** * 用於判斷是否捕獲當前child的觸控事件 * @param child 當前觸控的子View * @param pointerId * @return true:捕獲並解析 false:不處理 */ @Override public boolean tryCaptureView(View child, int pointerId) { return child == menuView || child == mainView; } /** * 獲取view水平方向的拖拽範圍 * @param child * @return */ @Override public int getViewHorizontalDragRange(View child) { return (int) dragRange; } /** * 控制child在水平方向的移動 * @param child * @param left 當前child的即將移動到的位置,left=chile.getLeft()+dx * @param dx 本次child水平方向移動的距離 * @return */ @Override public int clampViewPositionHorizontal(View child, int left, int dx) { if (child == mainView) { if (left < 0) { //限制mainView左邊界 left = 0; } if (left > dragRange) { //限制mainView右邊界 left = (int) dragRange; } } else if (child == menuView) { //menuView不可以滑動,設定left = left - dx; //滑動menuView的時候讓menuView不要移動,如果left = left - dx,這樣做的話menuView無法移動, //就不能讓maiView伴隨menuView移動 //left = left - dx; } return left; } /** * 當child的位置改變的時候執行,一般用來做其他子View跟隨該view移動 * @param changedView * @param left * @param top * @param dx * @param dy */ @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { super.onViewPositionChanged(changedView, left, top, dx, dy); if (changedView == menuView) { //固定menuView,不讓其移動 menuView.layout(0, 0, menuView.getMeasuredWidth(), menuView.getMeasuredHeight()); int newLeft = mainView.getLeft() + dx; if (newLeft < 0) { newLeft = 0; } if (newLeft > dragRange) { //限制mainView右邊界 newLeft = (int) dragRange; } //滑動menuView的時候讓mainView跟著移動 mainView.layout(newLeft, mainView.getTop() + dy, newLeft + mainView.getMeasuredWidth(), mainView.getBottom() + dy); } //計算mainView滑動的百分比 float fraction = mainView.getLeft() / dragRange; //執行伴隨動畫 executeAnim(fraction); //更改狀態,介面回撥 if (fraction == 0 && currentState != DragState.Close) { //更改為關閉狀態 currentState = DragState.Close; if (listener != null) { listener.onClose(); } } else if (fraction == 1f && currentState != DragState.Open) { //更改為開啟狀態 currentState = DragState.Open; if (listener != null) { listener.onOpen(); } } //將fraction暴露給外界 if (listener != null) { listener.onDraging(fraction); } } /** * 手指擡起的執行該方法 * @param releasedChild * @param xvel * @param yvel */ @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); if (mainView.getLeft() < dragRange / 2) { //在左半邊,平滑動畫 close(); } else { //在右半邊 open(); } //處理使用者的稍微滑動 if (xvel > 200 && currentState != DragState.Open) { open(); } else if (xvel < -200 && currentState != DragState.Close) { close(); } } }; /** * 開啟選單 */ public void open() { viewDragHelper.smoothSlideViewTo(mainView, (int) dragRange, mainView.getTop()); ViewCompat.postInvalidateOnAnimation(SlidingMenu.this); } /** * 關閉選單 */ public void close() { viewDragHelper.smoothSlideViewTo(mainView, 0, mainView.getTop()); ViewCompat.postInvalidateOnAnimation(SlidingMenu.this); } /** * 執行伴隨動畫 * * @param fraction */ private void executeAnim(float fraction) { //縮小mainView // mainView.setScaleX(1 - fraction * 0.2f);//0.8-1 //mainView.setScaleY(1 - fraction * 0.2f); ViewHelper.setScaleX(mainView, 1 - fraction * 0.2f); ViewHelper.setScaleY(mainView, 1 - fraction * 0.2f); //menuView移動動畫:-menuView.getMeasuredWidth() / 2 0 ViewHelper.setTranslationX(menuView, -menuView.getMeasuredWidth() / 2 + fraction * menuView.getMeasuredWidth() / 2); //menuView放大動畫 ViewHelper.setScaleX(menuView, 0.5f); ViewHelper.setScaleY(menuView, 0.5f); ViewHelper.setScaleX(menuView, 0.5f + 0.5f * fraction); ViewHelper.setScaleY(menuView, 0.5f + 0.5f * fraction); //menuView透明度動畫 ViewHelper.setAlpha(menuView, 0.3f); ViewHelper.setAlpha(menuView, 0.3f + 0.7f * fraction); //給SlidingMenu的背景新增黑色的遮罩效果:menuView拉出來的時候背景慢慢變淡 getBackground().setColorFilter((Integer) ColorUtil.evaluateColor (fraction, Color.BLACK, Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER); } @Override public void computeScroll() { super.computeScroll(); if (viewDragHelper.continueSettling(true)) { //如果動畫沒有結束,繼續執行動畫 ViewCompat.postInvalidateOnAnimation(SlidingMenu.this); } } private onDragStateChangeListener listener; public void setonDragStateChangeListener(onDragStateChangeListener listener) { this.listener = listener; } //介面 public interface onDragStateChangeListener { //開啟的回撥 void onOpen(); //關閉的回撥 void onClose(); //正在拖拽中的回撥 void onDraging(float fraction); } }

使用


/**
 * QQ側滑選單
 */
public class MainActivity extends AppCompatActivity {

    private ListView menu_listView, main_listview;

    private SlidingMenu mSlidingMenu;

    private ImageView ivHead;

    private MyLinearLayout myLinearLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        findViews();
        init();
        initListener();
    }

    private void init() {
        menu_listView.setAdapter(new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, Constant.sCheeseStrings) {
            //重寫getView(),可改變字型顏色
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                TextView textView = (TextView) super.getView(position, convertView, parent);
                textView.setTextColor(Color.WHITE);
                return textView;
            }
        });

        main_listview.setAdapter(new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, Constant.NAMES) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                View view = super.getView(position, convertView, parent);
                //先縮小view
                ViewHelper.setScaleX(view, 0.5f);
                ViewHelper.setScaleY(view, 0.5f);

                //以屬性動畫放大
                ViewPropertyAnimator.animate(view).scaleX(1f).setDuration(350).start();
                ViewPropertyAnimator.animate(view).scaleY(1f).setDuration(350).start();
                return view;
            }
        });
    }

    private void initListener() {
        mSlidingMenu.setonDragStateChangeListener(new SlidingMenu.onDragStateChangeListener() {
            @Override
            public void onOpen() {
                Log.i("info", "onOpen");
                //開啟的時候讓menu_listView隨機選中一個條目
                menu_listView.smoothScrollToPosition(new Random().nextInt(menu_listView.getCount()));
            }

            @Override
            public void onClose() {
                Log.i("info", "onClose");
                //關閉的時候讓主介面左上角的圖片抖一下
                TranslateAnimation animation = new TranslateAnimation(
                        Animation.RELATIVE_TO_SELF, 0,
                        Animation.RELATIVE_TO_SELF, 0.5f,
                        Animation.RELATIVE_TO_SELF, 0,
                        Animation.RELATIVE_TO_SELF, 0);
                animation.setDuration(500);
                animation.setInterpolator(new CycleInterpolator(4)); //設定迴圈移動次數
                ivHead.startAnimation(animation);

            }

            @Override
            public void onDraging(float fraction) {
                Log.i("info", "onDraging" + fraction);
                ivHead.setAlpha(1 - fraction);
            }
        });
        //把SlidingMenu設定給myLinearLayout
        myLinearLayout.setSlidingMenu(mSlidingMenu);
    }

    private void findViews() {
        menu_listView = (ListView) findViewById(R.id.menu_listview);
        main_listview = (ListView) findViewById(R.id.main_listview);
        mSlidingMenu = (SlidingMenu) findViewById(R.id.slidingMenu);
        ivHead = (ImageView) findViewById(R.id.iv_head);
        myLinearLayout = (MyLinearLayout) findViewById(R.id.my_layout);
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<com.xiaoyehai.slidingmenu.widget.SlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/slidingMenu"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg">

    <include layout="@layout/layout_menu" />

    <include layout="@layout/layout_main" />

</com.xiaoyehai.slidingmenu.widget.SlidingMenu>

layout_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingLeft="20dp"
    android:paddingTop="50dp" >

    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/head" />

    <ListView
        android:id="@+id/menu_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="5dp"
        android:listSelector="@android:color/transparent" >
    </ListView>

</LinearLayout>

layout_main.xml

<?xml version="1.0" encoding="utf-8"?>
<com.xiaoyehai.slidingmenu.widget.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/my_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#459BFE">

        <ImageView
            android:id="@+id/iv_head"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="15dp"
            android:background="@drawable/head" />
    </RelativeLayout>

    <ListView
        android:id="@+id/main_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:listSelector="@android:color/transparent"></ListView>

</com.xiaoyehai.slidingmenu.widget.MyLinearLayout>

當SlidingMenu開啟的時候,攔截並消費觸控事件,讓main_listview不能上下滑動

package com.xiaoyehai.slidingmenu.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

/**
 * 當SlidingMenu開啟的時候,攔截並消費觸控事件,讓main_listview不能上下滑動
 * Created by xiaoyehai on 2016/11/30.
 */
public class MyLinearLayout extends LinearLayout {

    private SlidingMenu slidingMenu;

    public void setSlidingMenu(SlidingMenu slidingMenu) {
        this.slidingMenu = slidingMenu;
    }

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

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

    public MyLinearLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (slidingMenu != null && slidingMenu.getCurrentState() == SlidingMenu.DragState.Open) {
            //當SlidingMenu開啟的時候,攔截並消費觸控事件
            return true;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (slidingMenu != null && slidingMenu.getCurrentState() == SlidingMenu.DragState.Open) {
            //當SlidingMenu開啟的時候,攔截並消費觸控事件
            if (event.getAction() == MotionEvent.ACTION_UP) {
                //點選mainview擡起時關閉選單
                slidingMenu.close();
            }
            return true;
        }
        return super.onTouchEvent(event);
    }
}

ColorUtil

public class ColorUtil {
    /**
     * 根據百分比,從一個顏色到另一個顏色的漸變
     *
     * @param fraction   百分比0-1
     * @param startValue 開始顏色
     * @param endValue   結束顏色
     * @return
     */
    public static Object evaluateColor(float fraction, Object startValue,
                                       Object endValue) {
        int startInt = (Integer) startValue;
        int startA = (startInt >> 24) & 0xff;
        int startR = (startInt >> 16) & 0xff;
        int startG = (startInt >> 8) & 0xff;
        int startB = startInt & 0xff;

        int endInt = (Integer) endValue;
        int endA = (endInt >> 24) & 0xff;
        int endR = (endInt >> 16) & 0xff;
        int endG = (endInt >> 8) & 0xff;
        int endB = endInt & 0xff;

        return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
                | (int) ((startR + (int) (fraction * (endR - startR))) << 16)
                | (int) ((startG + (int) (fraction * (endG - startG))) << 8)
                | (int) ((startB + (int) (fraction * (endB - startB))));
    }
}

Constant

package com.xiaoyehai.slidingmenu;

public interface Constant {
    public static final String[] sCheeseStrings = {
            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
            "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
            "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
            "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
            "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
            "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
            "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
            "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
    };

    public static final String[] NAMES = new String[]{"宋江", "盧俊義", "吳用",
            "公孫勝", "關勝", "林沖", "秦明", "呼延灼", "花榮", "柴進", "李應", "朱仝", "魯智深",
            "武松", "董平", "張清", "楊志", "徐寧", "索超", "戴宗", "劉唐", "李逵", "史進", "穆弘",
            "雷橫", "李俊", "阮小二", "張橫", "阮小五", " 張順", "阮小七", "楊雄", "石秀", "解珍",
            " 解寶", "燕青", "朱武", "黃信", "孫立", "宣贊", "郝思文", "韓滔", "彭玘", "單廷珪",
            "魏定國", "蕭讓", "裴宣", "歐鵬", "鄧飛", " 燕順", "楊林", "凌振", "蔣敬", "呂方",
            "郭 盛", "安道全", "皇甫端", "王英", "扈三娘", "鮑旭", "樊瑞", "孔明", "孔亮", "項充",
            "李袞", "金大堅", "馬麟", "童威", "童猛", "孟康", "侯健", "陳達", "楊春", "鄭天壽",
            "陶宗旺", "宋清", "樂和", "龔旺", "丁得孫", "穆春", "曹正", "宋萬", "杜遷", "薛永", "施恩",
    };
}