android 自定义布局之侧拉布局

                                     android 自定义布局之侧拉布局


侧拉布局是一个自定义的布局,类似QQ的侧拉


代码如下,

package com.example.android_project_two;

/**
 * Created by Administrator on 2017/6/21 0021.
 */

import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.widget.LinearLayout;

/**
 * @ 对外仅需设置的接口
 *
 * -判断左侧布局是否完全显示出来,或完全隐藏,滑动过程中此值无效
 * boolean SlidingLayout.isLeftLayoutVisible()
 *
 * -将屏幕滚动到右侧布局界面
 * void SlidingLayout.scrollToRightLayout()
 *
 * -将屏幕滚动到左侧布局界面
 * void SlidingLayout.scrollToLeftLayout()
 *
 */


public class SlidingLayout extends LinearLayout {

    private static final int SNAP_VELOCITY = 200;  //滚动显示和隐藏左侧布局时,手指滑动需要达到的速度。
    private VelocityTracker mVelocityTracker;      //用于计算手指滑动的速度。
    private int touchSlop;                         //在被判定为滚动之前用户手指可以移动的最大值。
    private int screenWidth;                       //屏幕宽度值
    private int leftEdge ;                         //左边最多可以滑动到的左边缘,值由左边布局的宽度来定,marginLeft到达此值之后,不能再减少。
    private int rightEdge = 0;                     //左边最多可以滑动到的右边缘,值恒为0,即marginLeft到达0之后,不能增加。
    private float xDown;                           //记录手指按下时的横坐标。
    private float yDown;                           //记录手指按下时的纵坐标。
    private float xMove;                           //记录手指移动时的横坐标。
    private float yMove;                           //记录手指移动时的纵坐标。
    private float xUp;                             //记录手机抬起时显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。
    private boolean isSliding;                     //是否正在滑动。的横坐标。
    private boolean isLeftLayoutVisible;           //左侧布局当前是
    private MarginLayoutParams leftLayoutParams;   //左侧布局的参数,通过此参数来重新确定左侧布局的宽度,以及更改leftMargin的值。
    private MarginLayoutParams rightLayoutParams;  //右侧布局的参数,通过此参数来重新确定右侧布局的宽度。
    private View leftLayout;                       //左侧布局对象。
    private View rightLayout;                      //右侧布局对象。


    //构造函数
    public SlidingLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);


        //获取屏幕宽度
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        screenWidth = wm.getDefaultDisplay().getWidth();

        //获取滑动的最短距离
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();


    }

    //创建VelocityTracker对象,并将触摸事件加入到VelocityTracker当中。
    private void createVelocityTracker(MotionEvent event)
    {
        if (mVelocityTracker == null)
        {
            mVelocityTracker = VelocityTracker.obtain();
        }

        mVelocityTracker.addMovement(event);
    }



    //拦截触摸事件
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {


        switch(event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                xDown = event.getRawX();
                yDown = event.getRawY();


                //当左边菜单完全显示时,点击右边的View,我们希望是拦截,而左边不拦截
                if(xDown > leftLayout.getLeft()+leftLayout.getWidth() && isLeftLayoutVisible)
                    return true;

                break;

            case MotionEvent.ACTION_MOVE:

                int distanceX=(int) (event.getRawX()-xDown);

                //水平滑动距离超过一定距离,拦截所有子view的touch事件,来处理自身onTouch事件来达到滑动的目的
                if(Math.abs(distanceX)>=touchSlop)
                {
                    return true;
                }
                break;
        }

        return false;
    }


    //触摸事件
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        // TODO 自动生成的方法存根

        createVelocityTracker(event);

        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                // 手指按下时,记录按下时的横坐标
                xDown = event.getRawX();
                yDown = event.getRawY();

                break;

            case MotionEvent.ACTION_MOVE:
                // 手指移动时,对比按下时的横坐标,计算出移动的距离,来调整右侧布局的leftMargin值,从而显示和隐藏左侧布局
                xMove = event.getRawX();
                yMove = event.getRawY();
                int distanceX = (int) (xMove - xDown);
                int distanceY = (int) (yMove - yDown);

                //只有触摸右边的View,才发生滑动
                if(xMove > leftLayout.getLeft()+leftLayout.getWidth())
                {

                    //向右滑
                    if (!isLeftLayoutVisible && distanceX >= touchSlop && (isSliding || Math.abs(distanceY) <= touchSlop))
                    {
                        isSliding = true;

                        leftLayoutParams.leftMargin = leftEdge + distanceX;

                        if (leftLayoutParams.leftMargin > rightEdge)
                        {
                            leftLayoutParams.leftMargin = rightEdge;
                        }

                    }
                    //向左滑
                    else if (isLeftLayoutVisible && -distanceX >= touchSlop)
                    {
                        isSliding = true;

                        leftLayoutParams.leftMargin = distanceX;

                        if (leftLayoutParams.leftMargin < leftEdge)
                        {
                            leftLayoutParams.leftMargin = leftEdge;
                        }

                    }

                    leftLayout.setLayoutParams(leftLayoutParams);

                }

                break;


            case MotionEvent.ACTION_UP:
                xUp = event.getRawX();
                int upDistanceX = (int) (xUp - xDown);

                if (isSliding)
                {
                    // 手指抬起时,进行判断当前手势的意图,从而决定是滚动到左侧布局,还是滚动到右侧布局
                    if (wantToShowLeftLayout())
                    {
                        if (shouldScrollToLeftLayout())
                        {
                            scrollToLeftLayout();
                        }
                        else
                        {
                            scrollToRightLayout();
                        }
                    }
                    else if (wantToShowRightLayout())
                    {
                        if (shouldScrollToRightLayout())
                        {
                            scrollToRightLayout();
                        }
                        else
                        {
                            scrollToLeftLayout();
                        }
                    }
                }
                //在左侧菜单完全显示时,我们希望的是点击右边的View可以发生恢复
                else if (upDistanceX < touchSlop && isLeftLayoutVisible && xUp > leftLayout.getLeft()+leftLayout.getWidth())
                {
                    scrollToRightLayout();
                }

                recycleVelocityTracker();

                break;

        }

        if (this.isEnabled())
        {
            if (isSliding)
            {
                unFocusBindView();
                return true;
            }
            if (isLeftLayoutVisible)
            {
                return true;
            }
            return false;
        }

        return true;
    }


    //将屏幕滚动到左侧布局界面,滚动速度设定为50.
    public void scrollToLeftLayout()
    {
        //传入第一个参数
        new ScrollTask().execute(50);
    }


    //将屏幕滚动到右侧布局界面,滚动速度设定为-50.
    public void scrollToRightLayout()
    {
        //传入第一个参数
        new ScrollTask().execute(-50);
    }

    //左侧布局是否完全显示出来,或完全隐藏,滑动过程中此值无效。
    public boolean isLeftLayoutVisible()
    {
        return isLeftLayoutVisible;
    }

    //在onLayout中重新设定左侧布局和右侧布局的参数。
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        super.onLayout(changed, l, t, r, b);

        if (changed)
        {

            // 获取左侧布局对象
            leftLayout = getChildAt(0);
            leftLayoutParams = (MarginLayoutParams) leftLayout.getLayoutParams();
            // 设置最左边距为负的左侧布局的宽度
            leftEdge = -leftLayoutParams.width;
            leftLayoutParams.leftMargin = leftEdge;
            leftLayout.setLayoutParams(leftLayoutParams);

            // 获取右侧布局对象
            rightLayout = getChildAt(1);
            rightLayoutParams = (MarginLayoutParams) rightLayout.getLayoutParams();
            rightLayoutParams.width = screenWidth;
            rightLayout.setLayoutParams(rightLayoutParams);
            rightLayout.setClickable(true);

        }

    }


    //判断当前手势的意图是不是想显示右侧布局。如果手指移动的距离是负数,且当前左侧布局是可见的,则认为当前手势是想要显示右侧布局。
    private boolean wantToShowRightLayout()
    {
        return xUp - xDown < 0 && isLeftLayoutVisible;
    }


    //判断当前手势的意图是不是想显示左侧布局。如果手指移动的距离是正数,且当前左侧布局是不可见的,则认为当前手势是想要显示左侧布局。
    private boolean wantToShowLeftLayout()
    {
        return xUp - xDown > 0 && !isLeftLayoutVisible;
    }

    //判断是否应该滚动将左侧布局展示出来。如果手指移动距离大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY,就认为应该滚动将左侧布局展示出来。
    private boolean shouldScrollToLeftLayout()
    {
        return xUp - xDown > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
    }

    //判断是否应该滚动将右侧布局展示出来。如果手指移动距离加上leftLayoutPadding大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY, 就认为应该滚动将右侧布局展示出来。
    private boolean shouldScrollToRightLayout()
    {
        return xDown - xUp > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
    }


    //获取手指在右侧布局的监听View上的滑动速度。
    private int getScrollVelocity()
    {
        mVelocityTracker.computeCurrentVelocity(1000);
        int velocity = (int) mVelocityTracker.getXVelocity();

        return Math.abs(velocity);
    }

    //回收VelocityTracker对象。
    private void recycleVelocityTracker()
    {
        mVelocityTracker.recycle();
        mVelocityTracker = null;
    }


    //使用可以获得焦点的控件在滑动的时候失去焦点。
    private void unFocusBindView()
    {
        if (rightLayout != null)
        {
            rightLayout.setPressed(false);
            rightLayout.setFocusable(false);
            rightLayout.setFocusableInTouchMode(false);
        }
    }


//*********************************************************************************************************************

    /**
     * @ 利用轻量级线程实现滑动 ,应用在手指松开的滑动动画
     */
    class ScrollTask extends AsyncTask<Integer, Integer, Integer>
    {

        //后台处理,入口参数对应第一个参数类型
        @Override
        protected Integer doInBackground(Integer... speed) {

            int leftMargin = leftLayoutParams.leftMargin;

            // 根据传入的速度来滚动界面,当滚动到达左边界或右边界时,跳出循环。
            while (true)
            {

                leftMargin = leftMargin + speed[0];
                if (leftMargin > rightEdge)
                {
                    leftMargin = rightEdge;
                    break;
                }
                if (leftMargin < leftEdge)
                {
                    leftMargin = leftEdge;
                    break;
                }

                //主动回调onProgressUpdate来更新界面(滑动)
                publishProgress(leftMargin);

                //使当前线程睡眠指定的毫秒数,为了要有滚动效果产生,每次循环使线程睡眠10毫秒,这样肉眼才能够看到滚动动画。
                try
                {
                    Thread.sleep(10);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }

            }
            if (speed[0] > 0)
            {
                isLeftLayoutVisible = true;
            }
            else
            {
                isLeftLayoutVisible = false;
            }

            isSliding = false;

            //返回对应第三个参数类型,并且把值传入onPostExecute
            return leftMargin;
        }

        //调用publishProgress时,回调这个方法,用来更新界面(滑动),入口参数对应第二个参数类型
        @Override
        protected void onProgressUpdate(Integer... leftMargin)
        {

            leftLayoutParams.leftMargin = leftMargin[0];
            leftLayout.setLayoutParams(leftLayoutParams);

            //使用可以获得焦点的控件在滑动的时候失去焦点。
            unFocusBindView();
        }

        //在doInBackground执行完成后执行界面更新,入口参数对应第三个参数类型
        @Override
        protected void onPostExecute(Integer leftMargin)
        {
            leftLayoutParams.leftMargin = leftMargin;
            leftLayout.setLayoutParams(leftLayoutParams);
        }
    }

}


在xml方面的布局就直接引用它就行,这个代码注释也是很多,很容易懂






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值