Android中侧滑菜单效果实现(主界面和菜单界面实现平移、缩放、滚动动画)

编写不易,如有转载,请声明出处:http://blog.csdn.net/zxc514257857/article/details/72602778

技术要点

  • 自定义侧滑控件SlideMenu
  • 主界面和菜单界面实现伴随移动
  • 根据拖拽的百分比实现平移、缩放、滚动等动画效果
  • ListView的基本使用
  • Butterknife 的使用

Demo展示图片

这里写图片描述

布局代码

//(layout)activity_main
<com.test.slidemenu.view.SlideMenu
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/slideMenu"
    android:background="@mipmap/bg"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!--两个子View-->

    <!--引入菜单布局 通过getChildAt(0)获取-->
    <include layout="@layout/layout_menu"/>

    <!--引入主界面布局 通过getChildAt(1)获取-->
    <include layout="@layout/layout_main"/>

</com.test.slidemenu.view.SlideMenu>
-------------------------------------------------------------------
//(layout)layout_main
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:background="#fff"
              android:id="@+id/main_linearlayout"
              android:layout_height="match_parent">

    <FrameLayout
        android:background="#18B4ED"
        android:layout_width="match_parent"
        android:layout_height="60dp">

        <ImageView
            android:layout_marginStart="20dp"
            android:layout_width="50dp"
            android:id="@+id/iv_headMainPic"
            android:layout_gravity="center_vertical"
            android:src="@mipmap/headpic"
            android:layout_height="50dp"/>
    </FrameLayout>

    <ListView
        android:overScrollMode="never"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/main_listview"/>
</LinearLayout>
-------------------------------------------------------------------
//(layout)layout_menu
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/menu_linearlayout"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >

    <ImageView android:id="@+id/iv_headMenuPic"
               android:layout_width="90dp"
               android:layout_height="90dp"
               android:background="@mipmap/headpic"
               android:layout_marginTop="50dp"
               android:layout_marginLeft="10dp"/>


    <ListView android:layout_width="match_parent"
              android:layout_marginTop="5dp"
              android:layout_height="match_parent"
              android:id="@+id/menu_listview"/>
</LinearLayout>
-------------------------------------------------------------------
//(layout)mainitem
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="60dp">

        <ImageView
            android:id="@+id/iv_pic"
            android:src="@mipmap/pic"
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="15dp"/>

        <TextView
            android:id="@+id/tv_contact"
            android:textSize="18sp"
            android:textColor="#FF3E96"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@+id/iv_pic"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </RelativeLayout>
</LinearLayout>

自定义侧滑控件代码

//(view)SlideMenu

import android.animation.ArgbEvaluator;
import android.animation.FloatEvaluator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

/**
 * 自定义SlideMenu控件 实现侧滑效果
 * 为了避免自己去实现onMeasure和onLayout方法 我们继承FrameLayout,系统已经实现好了宽高的测量和位置的摆放
 * 为什么不使用LinearLayout和RelativeLayout,因为FrameLayout代码简洁
 */

public class SlideMenu extends FrameLayout {

    private static final String TAG = "SlideMenu";
    // 通过它来进行View拖拽的实现
    private ViewDragHelper mViewDragHelper;
    private View mMenuView;
    private View mMainView;
    // mainView的拖拽范围
    private int dragRange;
    private int mMainWidth;
    private int mMenuWidth;
    private FloatEvaluator mFloatEvaluator;
    private ArgbEvaluator mArgbEvaluator;
    private OnSlideListener mOnSlideListener;
    // 表示当前状态: 关闭
    private DragState mDragState = DragState.Close;

    public enum DragState{
        Open ,Close
    }

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

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

    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initData();
    }

    private void initData(){
        // 父View、滑动敏感度、回调方法
        mViewDragHelper = ViewDragHelper.create(this , callback);
        // 浮点运算器
        mFloatEvaluator = new FloatEvaluator();
        // 颜色运算器
        mArgbEvaluator = new ArgbEvaluator();
    }

    /**
     * 该方法在ViewGroup将子View全部添加进来之后执行,但在onMeasure之前执行
     * 一般用来初始化子View的引用,但是还不能获取到子View的宽高
     */
    @Override
    protected void onFinishInflate() {
        mMenuView = getChildAt(0);
        mMainView = getChildAt(1);
        super.onFinishInflate();
    }

    /**
     * 当onMeasure方法执行完之后执行,在该方法中可以获取所有控件的宽高
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        dragRange = (int) (getMeasuredWidth()*0.6f);
        mMainWidth = mMainView.getMeasuredWidth();
        mMenuWidth = mMenuView.getMeasuredWidth();
        super.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 让ViewDragHelper帮助我们判断是否应该拦截
        boolean result = mViewDragHelper.shouldInterceptTouchEvent(ev);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 让ViewDragHelper帮助我们处理触摸事件
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        /**
         * 判断是否需要捕获View的触摸事件
         * 这里的child都是当前触摸的View
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            // 捕获mainView和menuView的触摸事件
            if(child == mMainView || child == mMenuView){
                return true;
            }
            return false;
        }

        /**
         * 当一个View被捕获触摸事件调用
         */
        @Override
        public void onViewCaptured(View capturedChild, int activePointerId) {
            Log.i(TAG , "onViewCaptured:" + activePointerId);
            super.onViewCaptured(capturedChild, activePointerId);
        }

        /**
         * 通过返回值判断滑动方向 只要大于0就可以正常水平垂直滑动
         */
        @Override
        public int getViewHorizontalDragRange(View child) {
            return 1;
        }

        /**
         * 用于捕获子View在水平方向上的移动
         * @param left:是ViewDragHelper帮我们计算好的View最新的left的值
         *            left = child.getLeft() + dx
         * @return 真正想要View的Left变成的值
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            // 限制主界面移动
            if(child == mMainView){
                left = clampLeft(left);
            }
            return left;
        }

//        /**
//         * 用于捕获子View在垂直方向上的移动
//         */
//        @Override
//        public int clampViewPositionVertical(View child, int top, int dy) {
//            return top;
//        }

        /**
         * 当View移动的时候调用,可以获取到手指移动的距离
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            // 当手指在menuView移动的时候,让mainView进行一个伴随的移动,但是menuView不动
            Log.i(TAG , "left: " + left + "dx: " + dx);
            // menuView固定住
            mMenuView.layout(0 ,0 , mMenuWidth , mMenuView.getBottom());

            if(changedView == mMenuView){
                int newLeft = mMainView.getLeft() + dx;
                // 限制newLeft否则,在menuView上滑动mainView无限制
                newLeft = clampLeft(newLeft);
                // 移动mainView
                mMainView.layout(newLeft ,0 , newLeft + mMainWidth , mMainView.getBottom());
            }

            // 增加伴随动画
            float fraction = mMainView.getLeft() * 1.0f / dragRange;
            // 得到百分比
            Log.i(TAG , "fraction" + fraction);
            // 执行动画效果
            execAnim(fraction);
            // 回调监听器的方法
            if(fraction == 0f && mDragState !=DragState.Close){
                mDragState =DragState.Close;
                // 说明关闭了
                if(mOnSlideListener != null){
                    mOnSlideListener.onClose();
                }
                // 说明打开了
            }else if(fraction == 1f && mDragState !=DragState.Open){
                mDragState =DragState.Open;
                if(mOnSlideListener != null){
                    mOnSlideListener.onOpen();
                }
            }

            // 说明在拖拽中
            if(mOnSlideListener != null){
                mOnSlideListener.onDraging(fraction);
            }
            super.onViewPositionChanged(changedView, left, top, dx, dy);
        }

        /**
         * 当手指从View上抬起的时候执行
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            // 判断mainView的left是否是大于dragRange的一半
            if(mMainView.getLeft() > dragRange / 2){
                // mainView平滑滚动到右边
                mViewDragHelper.smoothSlideViewTo(mMainView , dragRange , 0 );
            }else{
                // mainView平滑滚动到左边
                mViewDragHelper.smoothSlideViewTo(mMainView , 0 , 0 );
            }
            // 刷新操作
            ViewCompat.postInvalidateOnAnimation(SlideMenu.this);

            super.onViewReleased(releasedChild, xvel, yvel);
        }
    };

    /**
     * 根据拖拽的百分比来进行伴随动画效果
     */
    private void execAnim(float fraction) {
        // mainView的缩放
        mMainView.setScaleX(mFloatEvaluator.evaluate(fraction , 1.0f , 0.8f));
        mMainView.setScaleY(mFloatEvaluator.evaluate(fraction , 1.0f , 0.8f));

        // menuView的缩放
        mMenuView.setScaleX(mFloatEvaluator.evaluate(fraction , 0.4f , 1f));
        mMenuView.setScaleY(mFloatEvaluator.evaluate(fraction , 0.4f , 1f));

        // menuView 水平方向的平移
        mMenuView.setTranslationX(mFloatEvaluator.evaluate(fraction ,-mMenuWidth /2, 0));

        // 设置SlideMenu的颜色遮罩
        getBackground();
        if(getBackground() != null){
            // 由黑色变为透明的遮罩效果
            int color = (int) mArgbEvaluator.evaluate(fraction, Color.BLACK, Color.TRANSPARENT);
            getBackground().setColorFilter(color, PorterDuff.Mode.DARKEN);
        }
    }

    @Override
    public void computeScroll() {
        // 判断动画有没有结束,如果为true表示没有结束
        if(mViewDragHelper.continueSettling(true)){
            // 刷新操作
            ViewCompat.postInvalidateOnAnimation(SlideMenu.this);
        }
        super.computeScroll();
    }

    /**
     * 修正左边距
     */
    private int clampLeft(int left) {
        if(left > dragRange){
            left = dragRange;
        }else if(left < 0){
            left = 0;
        }
        return left;
    }

    public interface OnSlideListener{
        void onOpen();
        void onClose();
        void onDraging(float fraction);
    }

    public void setOnSlideListener(OnSlideListener mOnSlideListener){
        this.mOnSlideListener = mOnSlideListener;
    }
}

常量类代码

//conf(Constant)
public class Constant {
    public static final String[] SETTINGS = {"激活会员" , "QQ钱包" , "个性装扮" , "我的收藏" , "我的相册" , "我的文件"};
    public static final String[] CONSTACTS = {
            "宋江", "卢俊义","吴用","公孙胜","关胜","林冲","秦明","呼延灼","花荣","柴进","李应","朱仝",
            "鲁智深","武松","董平","张清","扬志","徐宁","索超","戴宗","刘唐","李逵","史进","穆弘",
            "雷横","李俊","阮小二","张横","阮小五","张顺","阮小七","杨雄","石秀","解珍","解宝","燕青",
            "朱武","黄信","孙立","宣赞","赦思文","韩滔","彭玑","单廷","魏定国","萧让","裴宣","欧鹏",
            "邓飞","燕顺","杨林","凌振","蒋敬","吕方","郭盛","安道全","皇浦端","王英","扈三娘","鲍旭",
            "樊瑞","孔明","孔亮","项充","李衮","金大坚","马麟","童威","童猛","孟康","候建","陈达",
            "杨春","郑天寿","陶宗旺","宋清","乐和","龚旺","丁得孙","穆春","曹正","宋万","杜迁","薛永",
            "施恩","李忠","周通","汤隆","杜兴","邹渊","邹润","朱贵","朱富","蔡福","蔡庆","李立",
            "李云","焦挺","石勇","孙新","顾大嫂","张青","孙二娘","王定六","郁保四","白胜","时迁","段景住"
    };
}

activity代码

//(activity)MainActivity
import android.animation.FloatEvaluator;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
import com.test.slidemenu.R;
import com.test.slidemenu.view.SlideMenu;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import butterknife.Bind;
import butterknife.ButterKnife;
import static com.test.slidemenu.conf.Constant.CONSTACTS;
import static com.test.slidemenu.conf.Constant.SETTINGS;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private FloatEvaluator mFloatEvaluator;

    @Bind(R.id.iv_headMenuPic)
    ImageView mIvHeadMenuPic;
    @Bind(R.id.menu_listview)
    ListView mMenuListview;
    @Bind(R.id.menu_linearlayout)
    LinearLayout mMenuLinearlayout;
    @Bind(R.id.iv_headMainPic)
    ImageView mIvHeadMainPic;
    @Bind(R.id.main_listview)
    ListView mMainListview;
    @Bind(R.id.main_linearlayout)
    LinearLayout mMainLinearlayout;
    @Bind(R.id.slideMenu)
    SlideMenu mSlideMenu;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
    }

    private void initView() {
        ButterKnife.bind(this);
        mFloatEvaluator = new FloatEvaluator();
    }

    private void initData() {
        mMenuListview.setAdapter(new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, SETTINGS) {
            @NonNull
            @Override
            // 修改布局属性
            public View getView(int position, View convertView, ViewGroup parent) {
                TextView textView = (TextView) super.getView(position, convertView, parent);
                textView.setTextColor(Color.parseColor("#FF3E96"));
                textView.setTextSize(18);
                textView.setSingleLine();
                return textView;
            }
        });
        mMenuListview.setDivider(null);
        mMenuListview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                Toast.makeText(MainActivity.this, SETTINGS[i], Toast.LENGTH_SHORT).show();
            }
        });

        List<Map<String, Object>> data = new ArrayList<>();
        String[] from = {"pic", "contact" };
        int[] to = {R.id.iv_pic, R.id.tv_contact};
        mMainListview.setAdapter(new SimpleAdapter(MainActivity.this, data, R.layout.mainitem, from, to));

        for (int i = 0; i < CONSTACTS.length; i++) {
            Map<String, Object> map = new HashMap<>();
            map.put("pic", R.mipmap.pic);
            map.put("contact", CONSTACTS[i]);
            data.add(map);
        }

        mMainListview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                Toast.makeText(MainActivity.this, CONSTACTS[i], Toast.LENGTH_SHORT).show();
            }
        });

        mSlideMenu.setOnSlideListener(new SlideMenu.OnSlideListener() {
            @Override
            public void onOpen() {
                Toast.makeText(MainActivity.this, "onOpen", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onClose() {
                Toast.makeText(MainActivity.this, "onClose", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDraging(float fraction) {
                Log.i(TAG, "fraction:" + fraction);
                // 沿着y轴旋转
                mIvHeadMainPic.setRotationY(mFloatEvaluator.evaluate(fraction, 0, 720));
            }
        });

        mIvHeadMenuPic.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "我是menuView头像", Toast.LENGTH_SHORT).show();
            }
        });

        mIvHeadMainPic.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "我是mainView头像", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

  使用Butterknife 需在Module的build.gradle中的dependencies节点下面添加:

compile 'com.jakewharton:butterknife:7.0.1'

  目前最新版本为8.6.0。Butterknife配合ButterKnife Zelezny插件使用更佳

  如果对Butterknife和ButterKnife Zelezny插件使用不太清楚可移步:Android中ButterKnife的使用(库和插件)http://blog.csdn.net/zxc514257857/article/details/59195960

Demo下载请移步:http://download.csdn.net/detail/zxc514257857/9848331


> ----------**因本人才疏学浅,如博客或Demo中有错误的地方请大家随意指出,与大家一起讨论,共同进步,谢谢!**----------
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DreamBackTo

感谢各位金主大大(* _ *)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值