Android 侧滑面板的实现(DragLayout)

一、概要:

1)应用场景:扩展主面板的功能
2)具体实现:
主要实现类ViewDragHelper,解决控件的拖拽问题。
里面有个参数mTouchSlop,最小敏感范围,值越小,越敏感
3)需要实现的动画
左面板动画,从小变大,从模糊到清楚,平移动画
右面板动画,从大到小,从清楚到模糊,平移动画
背景动画

效果图:
Android 侧滑面板的实现(DragLayout)_第1张图片

———————————————————————
有需求者请加qq:136137465,非诚勿扰
(java 架构师全套教程,共760G, 让你从零到架构师,每月轻松拿3万)
01.高级架构师四十二个阶段高
02.Java高级系统培训架构课程148课时
03.Java高级互联网架构师课程
04.Java互联网架构Netty、Nio、Mina等-视频教程
05.Java高级架构设计2016整理-视频教程
06.架构师基础、高级片
07.Java架构师必修linux运维系列课程
08.Java高级系统培训架构课程116课时
(送:hadoop系列教程,java设计模式与数据结构, Spring Cloud微服务, SpringBoot入门)
——————————————————————–

二、具体实现

1、创建类 DragLayout继承FrameLayout

public class DragLayout extends FrameLayout {
}

2、创建主文件

public class DragActivity extends Activity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_drag);
    }
}

3、在布局文件activity_drag把DragLayout作为容器


<com.android.imooc.draglayout.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/ll_top"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    LinearLayout>

    <LinearLayout
        android:id="@+id/ll_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    LinearLayout>

com.android.imooc.draglayout.DragLayout>

4、初始化ViewDragHelper

DragHelper = ViewDragHelper.create(this, cb);

5、把事件传递给ViewDragHelper

@Override
    public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }

6、初始化子控件并得到屏幕的宽高信息

@Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() < 2) {
            throw new IllegalArgumentException("子控件不能少于两个");
        }
        if (!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)) {
            throw new IllegalArgumentException("子控件必须是ViewGroup的子类");
        }

        mLeftConent = (ViewGroup) getChildAt(0);
        mMainConent = (ViewGroup) getChildAt(1);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mScreenHeight = getMeasuredHeight();
        mScreenWidth = getMeasuredWidth();
        mRange = (int) (mScreenWidth * 0.6f);
    }

6、实现回调方法

ViewDragHelper.Callback cb = new ViewDragHelper.Callback() {

        /**
         * 1.根据结果决定当前child是否可以拖拽
         * @child当前可被拖拽的view
         * @pointId手指的个数
         */
        @Override
        public boolean tryCaptureView(View child, int pointId) {
            //return child == mMainConent;
            return true;
        }

        /**
         * 2.从左边拖动时,不让拖动时越界
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            Logger.i(TAG, "clampViewPositionHorizontal left = " + left +  ",dx = " + dx );
            if (child == mMainConent) {
                left = fixLeft(left);
            }
            return left;
        }

        /**
         * 3.当view位置改变时的时候,需要处理的事情(更新状态,伴随动画,重绘界面)
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            Logger.i(TAG, "onViewPositionChanged left = " + left + ",top = " + top + ",dx = " + dx + ",dy = " +dy);

            int newLeft = 0;
            //固定左菜单,不让其移动,主内容移动
            if (changedView == mLeftConent) {
                mLeftConent.layout(0, 0, mScreenWidth, mScreenHeight);
                //拖拽左边时,移动主内容
                newLeft = mMainConent.getLeft() + dx;
                newLeft = fixLeft(newLeft);
                mMainConent.layout(newLeft, 0, newLeft + mScreenWidth, mScreenHeight);
            }
            //为了兼容底版本
            invalidate();
        }

手指释放时,
如果在中线的右边则layout到右边mrange
如果在中线的左边,则layout到0
Android 侧滑面板的实现(DragLayout)_第2张图片

7、实现打开关闭时动画的平滑移动

1)在上面的监听器里重写方法

/**
         * 4.view释放的时候触发
         * 
         * @xvel 水平方向的速度
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            Logger.i(TAG, "onViewReleased xvel = " + xvel + ",yvel = " + yvel);
            // 如果在中线的右侧,并且没拖动时,则打开
            if ((xvel == 0 && mMainConent.getLeft() > mRange / 2.0f) || xvel > 0) {
                open();
            } else {
                close();
            }

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

2)实现打开与关闭的方法

/**
     * 关闭菜单
     */
    protected void close(boolean isSmooth) {
        if (isSmooth) {
            // ObjectAnimator anim = ObjectAnimator.ofFloat(mMainConent,
            // propertyName, values)
            // 触发平滑动画
            if (mDragHelper.smoothSlideViewTo(mMainConent, 0, 0)) {
                // this 就是viewgroup
                ViewCompat.postInvalidateOnAnimation(this);
            }

        } else {
            mMainConent.layout(0, 0, mScreenWidth, mScreenHeight);
        }

    }

    private void close() {
        close(true);
    }

    /**
     * 打开菜单
     */
    protected void open(boolean isSmooth) {
        if (isSmooth) {
            // ObjectAnimator anim = ObjectAnimator.ofFloat(mMainConent,
            // propertyName, values)
            // 触发平滑动画
            if (mDragHelper.smoothSlideViewTo(mMainConent, mScreenWidth, 0)) {
                // this 就是viewgroup
                ViewCompat.postInvalidateOnAnimation(this);
            }

        } else {
            mMainConent.layout(mRange, 0, mScreenWidth + mRange, mScreenHeight);
        }
    }

    private void open() {
        open(true);
    }

3)重写主类里的computeScroll方法

/**
     * 持续的动画
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        // 如果返回true,动画还需继续执行
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

8、实现动画效果

为了能够实现缩小放到平移的动画,必须在onViewPositionChanged方法里添加dispatchDragEvent(newLeft)方法

protected void dispatchDragEvent(int newLeft) {
        //获得百分比,有了百分比就可执行各种动画
        float percent = newLeft * 1.0f / mRange;
        Logger.d(TAG, "percent == " + percent);

        //左面板动画,从小变大,从模糊到清楚, 
        //mLeftConent.setScaleX(0.5f * percent + 0.5f);
        //mLeftConent.setScaleY(0.5f * percent + 0.5f);
        ViewHelper.setScaleX(mLeftConent, evalute(percent, 0.5f, 1.0f));
        ViewHelper.setScaleY(mLeftConent, evalute(percent, 0.5f, 1.0f));
        // 平移动画 , -width/2 ->0 ,从外到内
        ViewHelper.setTranslationX(mLeftConent, evalute(percent, -mScreenWidth/2.0f, 0));
        //透明度从0.5f到 1.0f
        ViewHelper.setAlpha(mLeftConent, evalute(percent, 0.5f, 1.0f));

        //右面板动画,从大到小,从清楚到模糊,平移动画
        ViewHelper.setScaleX(mMainConent, evalute(percent, 1.0f, 0.8f));
        ViewHelper.setScaleY(mMainConent, evalute(percent, 1.0f, 0.8f));
        //ViewHelper.setTranslationX(mMainConent, evalute(percent, 0, mScreenWidth/2.0f));

        //背景动画    ,此动画必须在xml文件的主布局里添加背景图片
        getBackground().setColorFilter((Integer) evaluateColor(percent, Color.BLACK, Color.TRANSPARENT),  Mode.SRC_OVER);

    }

/**
     * 估值器,得到移动后的位置
     * @param percent
     * @param startValue
     * @param endValue
     * @return
     */
    public Float evalute(float percent, Number startValue, Number endValue){
        float start = startValue.floatValue();
        return start + percent *(endValue.floatValue() - start);
    }

    /**
     * 颜色变化过度
     * @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))));
    }

9、定义状态,接口

1)有三种状态,这里使用枚举进行定义

enum Status{
    Close,Open,Draging
}

2)定义接口,主要是为了提供给用户在各个状态下实现相关的方法

 public interface onDragStatusChangeListener{
        void close();
        void open();
        void drag();
    }
    private onDragStatusChangeListener mListener;
    public void setonDragStatusChangeListener(onDragStatusChangeListener listener){
        this.mListener = listener;
    }

3)更新状态,在dispatchDragEvent里

//更新状态
private void dispatchDragEvent()
{
    mStatus = updateStatus(percent);
}
private Status updateStatus(float percent) {
        return percent == 0 ? Status.CLOSE : (percent == 1? Status.OPEN : Status.DRAGING);
}

4)判断状态是否与以前的相同,不同则说明状态改变,进行回调

private void dispatchDragEvent()
{
       if (mListener != null) {
            mListener.drag(percent);
        }

        Status preStatus = mStatus;
        //更新状态
        mStatus = updateStatus(percent);
        //说明状态改变
        if (mStatus != preStatus) {
            if (mStatus == Status.CLOSE) {
                if (mListener != null) {
                    mListener.close();
                }
            }else if (mStatus == Status.OPEN) {
                if (mListener != null) {
                    mListener.open();
                }
            }
        }
}

10、添加了动作监听,下面就要在Activity里实现这些监听的方法

1)首先给布局文件DragLayou添加id
2)在Activity里findViewById初始化DragLayou
3)使用dragLayout.setonDragStatusChangeListener(dragListenter)添加监听
代码:

public class DragActivity extends Activity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_drag);
        DragLayout dragLayout = (DragLayout) findViewById(R.id.draglayout);
        dragLayout.setonDragStatusChangeListener(dragListenter);
    }

    private onDragStatusChangeListener  dragListenter = new onDragStatusChangeListener() {
        @Override
        public void open() {
            Util.showMsg(DragActivity.this, "open");
        }

        @Override
        public void drag(float percent) {
            Util.showMsg(DragActivity.this, "drag percent =" + percent);
        }

        @Override
        public void close() {
            Util.showMsg(DragActivity.this, "close");
        }
    };
}

11.初始化后添加数据

1)修改布局:


<com.android.imooc.draglayout.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/draglayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/ll_top"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_orange_light"
        android:orientation="vertical" >

        <ImageView
            android:id="@+id/iv_head_left"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:src="@drawable/head" />

        <ListView
            android:id="@+id/lv_left"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@id/iv_head_left"
            android:cacheColorHint="@android:color/transparent" >
        ListView>
    LinearLayout>

    <LinearLayout
        android:id="@+id/ll_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_green_dark"
        android:orientation="vertical" >

        <RelativeLayout
            android:id="@+id/ll_header"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="#18B4ED"
            android:gravity="center_vertical"
            android:orientation="horizontal" >

            <ImageView
                android:id="@+id/iv_head"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginLeft="10dp"
                android:scaleType="fitXY"
                android:src="@drawable/head" />

            <ImageView
                android:id="@+id/iv_head_right"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_alignParentRight="true"
                android:layout_marginRight="10dp"
                android:src="@drawable/btn_right_selector" />
        RelativeLayout>

        <ListView
            android:id="@+id/lv_main"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@id/ll_header" />
    LinearLayout>

com.android.imooc.draglayout.DragLayout>

2)在activity里初始化数据

private void initListView() {
        mLeftListView = (ListView) findViewById(R.id.lv_left);
        mMainListView = (ListView) findViewById(R.id.lv_main);

        mLeftListView.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, Cheeses.sCheeseStrings){
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                View view = super.getView(position, convertView, parent);
                if (view instanceof TextView) {
                    TextView tv = (TextView) view;
                    tv.setTextColor(Color.WHITE);
                }
                return view;
            }
        });
        mMainListView.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, Cheeses.NAMES));
    }

12、给右边主内容添加事件处理

1)在主内容缩到最小的时候,listview里的内容不能上下拉动,如何实现这样的功能呢?
事件当然不能由listview来处理,只能由其父控件LinearLayout处理,所以只有重写LinearLayout里的两个方法onInterceptTouchEvent与onTouchEvent。
其中onInterceptTouchEvent是拦截动作,返回true时不把事件传递给子控件。
那如何把DragLayout里的事件交给LinearLayout处理,我们添加一个setDragLayout方法
具体代码:

/**
 * @描述 TODO
 * @项目名称 App_imooc
 * @包名 com.android.imooc.draglayout
 * @类名 XLinearLayout
 * @author chenlin
 * @date 2015年5月22日 下午10:41:03
 */

public class XLinearLayout extends LinearLayout {
    private DragLayout mLayout;

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

    public XLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public XLinearLayout(Context context) {
        super(context);
    }

    public void setDragLayout(DragLayout layout) {
        mLayout = layout;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 如果是当前是关闭状态,按之前的方法判断
        if (mLayout.getStatus() == Status.CLOSE) {
            return super.onInterceptTouchEvent(ev);
        } else {
            // 不把事件传递给子控件
            return true;
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 如果是当前是关闭状态,按之前的方法处理
        if (mLayout.getStatus() == Status.CLOSE) {
            return super.onTouchEvent(event);
        } else {
            //手指抬起
            if (event.getAction() == MotionEvent.ACTION_UP) {
                mLayout.close();
            }
            // 不把事件传递给子控件
            return true;
        }

    }

}

2)然后把布局里的主面板的LinearLayout替换成com.android.imooc.draglayout.XLinearLayout
3 )在activity里把dragLayout设置到XLinearLayout

XLinearLayout xLinearLayout = (XLinearLayout) findViewById(R.id.ll_content);
xLinearLayout.setDragLayout(dragLayout);

13、源码下载

链接: http://pan.baidu.com/s/1geXHVP5 密码: jg93

你可能感兴趣的:(android,自定义view)