前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >侧滑、ViewDragHelper、属性动画

侧滑、ViewDragHelper、属性动画

作者头像
六月的雨
发布2022-01-17 20:15:49
9960
发布2022-01-17 20:15:49
举报
文章被收录于专栏:Android开发指南Android开发指南

实现这样的效果:

## 侧滑面板(对ViewGroup的自定义) * 应用场景: 扩展主面板的功能 * 功能实现: > 1. ViewDragHelper: Google2013年IO大会提出的, > 解决界面控件拖拽移动问题. (v4包下) > 2. mTouchSlop 最小敏感范围, 值越小, 越敏感 * 伴随动画: > 1. 左面板: 缩放动画, 平移动画, 透明度动画 > 2. 主面板: 缩放动画 > 3. 背景动画: 亮度变化 (颜色变化) * 状态监听\触摸优化: > 1. 设置并更新状态 > 2. 触摸优化: 重写ViewGroup里onInterceptTouchEvent和onTouchEvent 新v4、看大小 nineoldandroids.jar 属性动画,兼容9个低版本 ActionBarSherlock

布局:

代码语言:javascript
复制
<com.drag.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/dl"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg"
    tools:context=".MainActivity" >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="50dp"
        android:paddingLeft="10dp"
        android:paddingRight="50dp"
        android:paddingTop="50dp" >
        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@drawable/head" />
        <ListView
            android:id="@+id/lv_left"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
        </ListView>
    </LinearLayout>
    <com.drag.MyLinearLayout
        android:id="@+id/mll"
        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="#18B6EF"
            android:gravity="center_vertical" >
            <ImageView
                android:id="@+id/iv_header"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginLeft="15dp"
                android:src="@drawable/head" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:text="Header" />
        </RelativeLayout>
        <ListView
            android:id="@+id/lv_main"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
        </ListView>
    </com.drag.MyLinearLayout>
</com.drag.DragLayout>

  Utils:单例的toast

代码语言:javascript
复制
public class Utils {
    public static Toast mToast;
    public static void showToast(Context mContext, String msg) {
        if (mToast == null) {
            mToast = Toast.makeText(mContext, "", Toast.LENGTH_SHORT);
        }
        mToast.setText(msg);
        mToast.show();
    }
     
    /**
     * dip 转换成 px
     * @param dip
     * @param context
     * @return
     */
    public static float dip2Dimension(float dip, Context context) {
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics);
    }
    /**
     * @param dip
     * @param context
     * @param complexUnit {@link TypedValue#COMPLEX_UNIT_DIP} {@link TypedValue#COMPLEX_UNIT_SP}}
     * @return
     */
    public static float toDimension(float dip, Context context, int complexUnit) {
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        return TypedValue.applyDimension(complexUnit, dip, displayMetrics);
    }
    /** 获取状态栏高度
     * @param v
     * @return
     */
    public static int getStatusBarHeight(View v) {
        if (v == null) {
            return 0;
        }
        Rect frame = new Rect();
        v.getWindowVisibleDisplayFrame(frame);
        return frame.top;
    }
    public static String getActionName(MotionEvent event) {
        String action = "unknow";
        switch (MotionEventCompat.getActionMasked(event)) {
        case MotionEvent.ACTION_DOWN:
            action = "ACTION_DOWN";
            break;
        case MotionEvent.ACTION_MOVE:
            action = "ACTION_MOVE";
            break;
        case MotionEvent.ACTION_UP:
            action = "ACTION_UP";
            break;
        case MotionEvent.ACTION_CANCEL:
            action = "ACTION_CANCEL";
            break;
        case MotionEvent.ACTION_SCROLL:
            action = "ACTION_SCROLL";
            break;
        case MotionEvent.ACTION_OUTSIDE:
            action = "ACTION_SCROLL";
            break;
        default:
            break;
        }
        return action;
    }
}

 DragLayout:

代码语言:javascript
复制
/**
 * 侧滑面板
 * @author poplar
 *
 */
public class DragLayout extends FrameLayout {
    private static final String TAG = "TAG";
    private ViewDragHelper mDragHelper;
    private ViewGroup mLeftContent;
    private ViewGroup mMainContent;
    private OnDragStatusChangeListener mListener;
    private Status mStatus = Status.Close;
     
    /**
     * 状态枚举
     */
    public static enum Status {
        Close, Open, Draging;
    }
    public interface OnDragStatusChangeListener{
        void onClose();
        void onOpen();
        void onDraging(float percent);
    }
     
    public Status getStatus() {
        return mStatus;
    }
    public void setStatus(Status mStatus) {
        this.mStatus = mStatus;
    }
    public void setDragStatusListener(OnDragStatusChangeListener mListener){
        this.mListener = mListener;
    }
     
    public DragLayout(Context context) {
        this(context, null);
    }
    public DragLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public DragLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
         
        // a.初始化 (通过静态方法)
        mDragHelper = ViewDragHelper.create(this , mCallback);
         
    }
     
    ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
        // c. 重写事件
         
        // 1. 根据返回结果决定当前child是否可以拖拽
        // child 当前被拖拽的View
        // pointerId 区分多点触摸的id
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            Log.d(TAG, "tryCaptureView: " + child);
            return true;
        };
         
        @Override
        public void onViewCaptured(View capturedChild, int activePointerId) {
            Log.d(TAG, "onViewCaptured: " + capturedChild);
            // 当capturedChild被捕获时,调用.
            super.onViewCaptured(capturedChild, activePointerId);
        }
        @Override
        public int getViewHorizontalDragRange(View child) {
            // 返回拖拽的范围, 不对拖拽进行真正的限制. 仅仅决定了动画执行速度
            return mRange;
        }
         
        // 2. 根据建议值 修正将要移动到的(横向)位置   (重要)
        // 此时没有发生真正的移动
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            // child: 当前拖拽的View
            // left 新的位置的建议值, dx 位置变化量
            // left = oldLeft + dx;
            Log.d(TAG, "clampViewPositionHorizontal: "
                    + "oldLeft: " + child.getLeft() + " dx: " + dx + " left: " +left);
             
            if(child == mMainContent){
                left = fixLeft(left);
            }
            return left;
        }
        // 3. 当View位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面)
        // 此时,View已经发生了位置的改变
        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                int dx, int dy) {
            // changedView 改变位置的View
            // left 新的左边值
            // dx 水平方向变化量
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            Log.d(TAG, "onViewPositionChanged: " + "left: " + left + " dx: " + dx);
             
            int newLeft = left;
            if(changedView == mLeftContent){
                // 把当前变化量传递给mMainContent
                newLeft = mMainContent.getLeft() + dx;
            }
             
            // 进行修正
            newLeft = fixLeft(newLeft);
             
            if(changedView == mLeftContent) {
                // 当左面板移动之后, 再强制放回去.
                mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
                mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
            }
            // 更新状态,执行动画
            dispatchDragEvent(newLeft);
             
            // 为了兼容低版本, 每次修改值之后, 进行重绘
            invalidate();
        }
        // 4. 当View被释放的时候, 处理的事情(执行动画)
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            // View releasedChild 被释放的子View
            // float xvel 水平方向的速度, 向右为+
            // float yvel 竖直方向的速度, 向下为+
            Log.d(TAG, "onViewReleased: " + "xvel: " + xvel + " yvel: " + yvel);
            super.onViewReleased(releasedChild, xvel, yvel);
             
            // 判断执行 关闭/开启
            // 先考虑所有开启的情况,剩下的就都是关闭的情况
            if(xvel == 0 && mMainContent.getLeft() > mRange / 2.0f){
                open();
            }else if (xvel > 0) {
                open();
            }else {
                close();
            }
             
        }
        @Override
        public void onViewDragStateChanged(int state) {
            // TODO Auto-generated method stub
            super.onViewDragStateChanged(state);
        }
    };
     
    /**
     * 根据范围修正左边值
     * @param left
     * @return
     */
    private int fixLeft(int left) {
        if(left < 0){
            return 0;
        }else if (left > mRange) {
            return mRange;
        }
        return left;
    }
     
    protected void dispatchDragEvent(int newLeft) {
        float percent = newLeft * 1.0f/ mRange;
        //0.0f -> 1.0f
        Log.d(TAG, "percent: " + percent);
         
        if(mListener != null){
            mListener.onDraging(percent);
        }
         
        // 更新状态, 执行回调
        Status preStatus = mStatus;
        mStatus = updateStatus(percent);
        if(mStatus != preStatus){
            // 状态发生变化
            if(mStatus == Status.Close){
                // 当前变为关闭状态
                if(mListener != null){
                    mListener.onClose();
                }
            }else if (mStatus == Status.Open) {
                if(mListener != null){
                    mListener.onOpen();
                }
            }
        }
         
//      * 伴随动画:
        animViews(percent);
         
    }
    private Status updateStatus(float percent) {
        if(percent == 0f){
            return Status.Close;
        }else if (percent == 1.0f) {
            return Status.Open;
        }
        return Status.Draging;
    }
    private void animViews(float percent) {
        //      > 1. 左面板: 缩放动画, 平移动画, 透明度动画
                    // 缩放动画 0.0 -> 1.0 >>> 0.5f -> 1.0f  >>> 0.5f * percent + 0.5f
            //      mLeftContent.setScaleX(0.5f + 0.5f * percent);
            //      mLeftContent.setScaleY(0.5f + 0.5f * percent);
                    ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f));
                    ViewHelper.setScaleY(mLeftContent, 0.5f + 0.5f * percent);
                    // 平移动画: -mWidth / 2.0f -> 0.0f
                    ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth / 2.0f, 0));
                    // 透明度: 0.5 -> 1.0f
                    ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.5f, 1.0f));
                 
        //      > 2. 主面板: 缩放动画
                    // 1.0f -> 0.8f
                    ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f));
                    ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f));
                     
        //      > 3. 背景动画: 亮度变化 (颜色变化)
                    getBackground().setColorFilter((Integer)evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), Mode.SRC_OVER);
    }
     
    /**
     * 估值器,0-100,一半50百分之=50,10-100
     * @param fraction
     * @param startValue
     * @param endValue
     * @return
     */
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
    /**
     * 颜色变化过度
     * @param fraction
     * @param startValue
     * @param endValue
     * @return
     */
    public 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))));
    }
    @Override
    public void computeScroll() {
        super.computeScroll();
         
        // 2. 持续平滑动画 (高频率调用)
        if(mDragHelper.continueSettling(true)){
            //  如果返回true, 动画还需要继续执行
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
     
    public void close(){
        close(true);
    }
    /**
     * 关闭
     */
    public void close(boolean isSmooth) {
        int finalLeft = 0;
        if(isSmooth){
            // 1. 触发一个平滑动画
            if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
                // 返回true代表还没有移动到指定位置, 需要刷新界面.
                // 参数传this(child所在的ViewGroup)
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }else {
            mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
        }
    }
     
    public void open(){
        open(true);
    }
    /**
     * 开启
     */
    public void open(boolean isSmooth) {
        int finalLeft = mRange;
        if(isSmooth){
            // 1. 触发一个平滑动画
            if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
                // 返回true代表还没有移动到指定位置, 需要刷新界面.
                // 参数传this(child所在的ViewGroup)
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }else {
            mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
        }
    }
    private int mHeight;
    private int mWidth;
    private int mRange;
     
    // b.传递触摸事件
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 传递给mDragHelper
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        try {
            mDragHelper.processTouchEvent(event);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 返回true, 持续接受事件
        return true;
    }
     
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        // Github
        // 写注释
        // 容错性检查 (至少有俩子View, 子View必须是ViewGroup的子类)
         
        if(getChildCount() < 2){
            throw new IllegalStateException("布局至少有俩孩子. Your ViewGroup must have 2 children at least.");
        }
        if(!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)){
            throw new IllegalArgumentException("子View必须是ViewGroup的子类. Your children must be an instance of ViewGroup");
        }
         
        mLeftContent = (ViewGroup) getChildAt(0);
        mMainContent = (ViewGroup) getChildAt(1);
    }
     
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 当尺寸有变化的时候调用
         
        mHeight = getMeasuredHeight();
        mWidth = getMeasuredWidth();
         
        // 移动的范围
        mRange = (int) (mWidth * 0.6f);
         
    }
     
}

  MyLinearLayout:主页面,在打开侧边栏或者拖拽时不让主页面里的listview滑动

代码语言:javascript
复制
public class MyLinearLayout extends LinearLayout {
    private DragLayout mDragLayout;
    public MyLinearLayout(Context context) {
        super(context);
    }
    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
     
    public void setDraglayout(DragLayout mDragLayout){
        this.mDragLayout = mDragLayout;
    }
     
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 如果当前是关闭状态, 按之前方法判断
        if(mDragLayout.getStatus() == Status.Close){
            return super.onInterceptTouchEvent(ev);
        }else {
            return true;
        }
    }
     
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 如果当前是关闭状态, 按之前方法处理
        if(mDragLayout.getStatus() == Status.Close){
            return super.onTouchEvent(event);
        }else {
            // 手指抬起, 执行关闭操作
            if(event.getAction() == MotionEvent.ACTION_UP){
                mDragLayout.close();
            }
             
            return true;
        }
    }
}

  MainActivity

代码语言:javascript
复制
public class MainActivity extends Activity {
    private static final String TAG = "TAG";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
         
        final ListView mLeftList = (ListView) findViewById(R.id.lv_left);
        final ListView mMainList = (ListView) findViewById(R.id.lv_main);
        final ImageView mHeaderImage = (ImageView) findViewById(R.id.iv_header);
        MyLinearLayout mLinearLayout = (MyLinearLayout) findViewById(R.id.mll);
         
        // 查找Draglayout, 设置监听
        DragLayout mDragLayout = (DragLayout) findViewById(R.id.dl);
        // 设置引用
        mLinearLayout.setDraglayout(mDragLayout);
         
        mDragLayout.setDragStatusListener(new OnDragStatusChangeListener() {
             
            @Override
            public void onOpen() {
                Utils.showToast(MainActivity.this, "onOpen");
                // 左面板ListView随机设置一个条目
                Random random = new Random();
                 
                int nextInt = random.nextInt(50);
                mLeftList.smoothScrollToPosition(nextInt);
                 
            }
             
            @Override
            public void onDraging(float percent) {
                Log.d(TAG, "onDraging: " + percent);// 0 -> 1
                // 更新图标的透明度
                // 1.0 -> 0.0
                ViewHelper.setAlpha(mHeaderImage, 1 - percent);
            }
             
            @Override
            public void onClose() {
                Utils.showToast(MainActivity.this, "onClose");
                // 让图标晃动
//              mHeaderImage.setTranslationX(translationX)
                ObjectAnimator mAnim = ObjectAnimator.ofFloat(mHeaderImage, "translationX", 15.0f);
                mAnim.setInterpolator(new CycleInterpolator(4));
                mAnim.setDuration(500);
                mAnim.start();
            }
        });
         
        mLeftList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.sCheeseStrings){//Cheeses自己定义的,存放的一些字符串
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                View view = super.getView(position, convertView, parent);
                TextView mText = ((TextView)view);
                mText.setTextColor(Color.WHITE);
                return view;
            }
        });
         
        mMainList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.NAMES));
         
         
         
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015-12-12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档