android---自定义左滑右滑菜单

没有使用第三方类库,纯代码定制.主要用到的知识如下,

我们知道,不管是自定义View还是系统提供的TextView这些,它们都必须放置在LinearLayout等一些ViewGroup中,因此理论上我们可以很好的理解onMeasure(),onLayout(),onDraw()这三个函数:1.View本身大小多少,这由onMeasure()决定;2.View在ViewGroup中的位置如何,这由onLayout()决定;3.绘制View,onDraw()定义了如何绘制这个View。

一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。它有三种模式:UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;AT_MOST(至多),子元素至多达到指定大小的值。

  它常用的三个函数:

  1.static int getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一)

  2.static int getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)

  3.static int makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式)

public static final int ACTION_DOWN = 0; // 按下事件
public static final int ACTION_UP = 1; // 抬起事件
public static final int ACTION_MOVE = 2; // 手势移动事件
public static final int ACTION_CANCEL = 3; // 取消

还有触摸事件,滚动的实现等,代码注释很详细

public class MainUI extends RelativeLayout {

    private Context context;
    private FrameLayout middleMenu,leftMenu,rightMenu;
    //设置模版
    private FrameLayout middlemask;
    private Scroller scroller;

    //属于自定义view,这里就需要用到这两个构造函数
    public MainUI(Context context){
        super(context);
        inintView(context);
    }

    public MainUI(Context context, AttributeSet attrs) {
        super(context, attrs);
        inintView(context);
    }
    //初始化各种变量
    public void inintView(Context context){
        this.context = context;
        //然后我们需要三个界面布局,这里用framelayout来承载
        //设置滑动的是当前content和滑动样式
        scroller = new Scroller(context,new DecelerateInterpolator());
        leftMenu = new FrameLayout(context);
        middleMenu = new FrameLayout(context);
        rightMenu = new FrameLayout(context);
        middlemask = new FrameLayout(context);
        //为了区分给每个部分设置颜色
        leftMenu.setBackgroundColor(Color.RED);
        middleMenu.setBackgroundColor(Color.BLUE);
        rightMenu.setBackgroundColor(Color.GREEN);
        middlemask.setBackgroundColor(Color.GRAY);
        middlemask.setAlpha(0);
        //添加到当前布局
        addView(leftMenu);
        addView(middleMenu);
        addView(rightMenu);
        addView(middlemask);

        //接下来要测量宽度,好用来给这三个部分分别设置宽度,放在onmeasure中
    }

    /**这个方法决定view本身的大小
     * 这里的两个参数分别是屏幕的宽和高
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //中间的宽度正好是整个屏幕
        middleMenu.measure(widthMeasureSpec, heightMeasureSpec);
        middlemask.measure(widthMeasureSpec, heightMeasureSpec);
        //旁边的则最多为屏幕的80%
        int realWidth = MeasureSpec.getSize(widthMeasureSpec);
        int tempWidthMeasure = MeasureSpec.makeMeasureSpec(
                (int)(realWidth*0.8f),MeasureSpec.EXACTLY);
        leftMenu.measure(tempWidthMeasure, heightMeasureSpec);
        rightMenu.measure(tempWidthMeasure, heightMeasureSpec);
    }

    /**这个方法决定view在layout中的位置
     *四个参数对应屏幕 左上右下
     * @param changed
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //中间菜单是中间的屏幕
        middleMenu.layout(l, t, r, b);
        middlemask.layout(l, t, r, b);
        //左边的菜单则左边界要扩充
        leftMenu.layout(l - leftMenu.getMeasuredWidth(), t, r, b);
        //右边界左右都要设置
        rightMenu.layout(
                l + middleMenu.getMeasuredWidth(),
                t,
                l + middleMenu.getMeasuredWidth()
                        + rightMenu.getMeasuredWidth(), b);
        //接下来添加滑动事件
    }


    private boolean isTsetCompete;
    /**
     * 处理相应的触摸事件
     * @param ev
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        if (!isTsetCompete){
            getEventType(ev);
            return true;
        }
        //如果是左右滑动
        if (isleftrightmove){
            switch(ev.getActionMasked()){
                case MotionEvent.ACTION_MOVE:
                    //得到滑动距离
                    int currScrollX = getScrollX();
                    //得到移动距离
                    int dis_x = (int)(ev.getX() -point.x);
                    //他俩差值肯定在20之间
                    int expectX = -dis_x +currScrollX;
                    int finalx=0;
                    if (expectX<0){
                        //右滑距离
                        finalx = Math.max(expectX,-leftMenu.getMeasuredWidth());
                    }else{
                        //左滑距离
                        finalx = Math.min(expectX,rightMenu.getMeasuredWidth());
                    }
                    scrollTo(finalx,0);
                    point.x = (int) ev.getX();
                    break;
                //下面判断继续滑动或者手指离开屏幕
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    //判断如果滑动距离大于一半则自动滑动出来,否则滑动回去
                    currScrollX = getScrollX();
                    if (Math.abs(currScrollX) > leftMenu.getMeasuredWidth() >>1) {
                        if (currScrollX < 0) {
                            scroller.startScroll(currScrollX, 0, -leftMenu.getMeasuredWidth() - currScrollX, 0);
                        }else {
                            scroller.startScroll(currScrollX,0,leftMenu.getMeasuredWidth()-currScrollX,0);
                        }
                    }else{
                        scroller.startScroll(currScrollX,0,-currScrollX,0);
                    }
                    //用于屏幕刷新
                    invalidate();
                    isleftrightmove = false;
                    isTsetCompete = false;
                    break;
            }
        }

        return super.dispatchTouchEvent(ev);
    }

    /**
     * 在滑动改变距离的同时改变透明度,这种方式很好,因为会先调用父类的方法,所以不会影响到原来程序的运行
     * @param x
     * @param y
     */
    @Override
    public void scrollTo(int x, int y) {
        super.scrollTo(x, y);
        int cruX = Math.abs(getScrollX());
        float fo = cruX / (float)middleMenu.getMeasuredWidth();
        middlemask.setAlpha(fo);
    }

    /**
     * 滚动条的回调方法
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (!scroller.computeScrollOffset()){
            return;
        }
        int tempX = scroller.getCurrX();
        scrollTo(tempX,0);
    }

    private Point point = new Point();
    private static final int TSET_DIS = 20;
    private boolean isleftrightmove;
    /**
     * 用于判断触摸事件类型的函数
     * @param ev
     */
    private void getEventType(MotionEvent ev) {
        switch(ev.getActionMasked()){
            case MotionEvent.ACTION_DOWN:
                //得到当前坐标
                point.x = (int) ev.getX();
                point.y = (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                int dX = Math.abs((int)ev.getX()-point.x);
                int dY = Math.abs((int)ev.getY()-point.y);
                //左右滑动
                if (dX >=TSET_DIS && dX>dY){
                    isleftrightmove = true;
                    isTsetCompete = true;
                    //为了滑动后可以再次滑动
                    point.x = (int) ev.getX();
                    point.y = (int) ev.getY();
                }else if (dY>=TSET_DIS&&dY>dX){
                    isleftrightmove = false;
                    isTsetCompete = false;
                    point.x = (int) ev.getX();
                    point.y = (int) ev.getY();
                }

                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:

                break;
        }
    }
}

这里写图片描述
这里写图片描述
这里写图片描述

版权声明:本文为博主原创文章,未经博主允许不得转载。

posted on 2015-08-24 20:18  牛李  阅读(599)  评论(0编辑  收藏  举报

导航