Android实现自定义滑动选择器

Android实现自定义滑动选择器_第1张图片


自定义一个滑动选择器首先需要自定义一个Java类,

在这里将其命名为TestScroller,让其继承View,实现所有的构造函数,如下图

public class TestScroller extends View{
    public TestScroller(Context context) {
        super(context);
    }

    public TestScroller(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TestScroller(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public TestScroller(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
}

要实现滑动选择,需要重写两个方法:

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }


    onDraw()会在View需要绘制的时候执行,例如TextView的字都是在这里绘制的。View显示和移动都是onDraw()的结果,只是由于绘制速度太快,才会给人移动的感觉。执行 invalid()后,onDraw()就会立即执行一次。我们可以通过调用 invalid(),来手动重绘。

    onTouchEvent()方法将会在你的手指触碰到控件的时候执行,然后我们调用onDraw(),实现View的滑动。

    

    首先我们先完成开头图片的数字,当然,是不能滑动的。

    Canvas是画布,既然是画布,那肯定还需要一个画笔Paint。有了画布和画笔之后,我们才能开始“作画”。

    首先我们需要初始化好我们需要的东西:

    

 private void init(){
        mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);//是使位图抗锯齿的标志
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setTextSize(mTextSize);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            mPaint.setColor(getContext().getColor(R.color.black));
        }
    }

    然后把init()放到每一个构造函数里。

    然后我们还需要获取View的宽高,因为我们要据此确定绘制的位置。

    

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth=getMeasuredWidth();
        mHeight=getMeasuredHeight();
    }

    然后我们就可以在onDraw()里绘制数字

@Override
    protected void onDraw(Canvas canvas) {
        for (int i=0;i<=10;i++){
            canvas.drawText(String.valueOf(i),mWidth/2,mTextSize+i*mDistance,mPaint);
        }
    }

        drawText四个参数分别为

    1.绘制的字符

    2.字符的X坐标

    3.字符的baseline

    Android实现自定义滑动选择器_第2张图片

    4.画笔

    mDistance则是每个字符之间的上下距离,最后的结果就是

    Android实现自定义滑动选择器_第3张图片


    接下来我们就开始实现滑动功能。这次我们需要在onTouchEvent里进行操作。

    函数的返回值如果为true则表示,这次触摸事件由该控件处理了,就不会传递给父布局处理。

    参数MotionEven则包含各种关于触摸点的信息,在这里我们只需要了解以下几个方法。

    1.getActionMasked()。该函数主要有以下三个返回值

    ·MotionEvent.ACTION_DOWN    按下    

    ·MotionEvent.ACTION_MOVE    移动

    ·MotionEvent.ACTION_DOWN    抬起

    2.getRawX,getRawY。可以获得触摸点相对于屏幕的x,y坐标

    然后我们就可以开始了。


    首先定义两个属性。

    ·mMoveLength    

    手指滑动的距离

    ·mLastY         

    手指上一个所在的Y坐标,如果手指从Y坐标 5 移到了 10,那么当手指位置在10的时候,此时mLastY==5;

        然后我们在onTouchEvent中进行switch判断

    1.ACTION_DOWN,按下手指

    mLastY=event.getRawY(),记录下第一次落点的Y坐标r


    2.ACTION_MOVE,移动手指

    mMoveLength+=event.getRawY()-mLastY,记录下移动的距离,同时通过调用invalid(),进行重绘。在onDraw()方法里,我们修改为

for (int i=0;i<=10;i++){
            canvas.drawText(String.valueOf(i),mWidth/2,mTextSize+i*mDistance+mMoveLength,mPaint);
        }

    在第三个参数内,加入了mMoveLength。


    3.ACTION_UP,抬起手指  

    这里暂时还不需要操作,后面才需要用到。

    最后的效果如图

    Android实现自定义滑动选择器_第4张图片

    目前为止的代码如下

    

public class TestScroller extends View{
    private Paint mPaint;
    private int mWidth;
    private int mHeight;
    private float mTextSize=60;
    private int mDistance=200;
    private float mMoveLength;
    private float mLastY;
    public TestScroller(Context context) {
        super(context);
        init();
    }

    public TestScroller(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public TestScroller(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public TestScroller(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth=getMeasuredWidth();
        mHeight=getMeasuredHeight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        for (int i=0;i<=10;i++){
            canvas.drawText(String.valueOf(i),mWidth/2,mTextSize+i*mDistance+mMoveLength,mPaint);
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()){
            case MotionEvent.ACTION_DOWN:
                mLastY=event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                mMoveLength+=event.getRawY()-mLastY;
                mLastY=event.getRawY();
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }
    private void init(){
        mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setTextSize(mTextSize);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            mPaint.setColor(getContext().getColor(R.color.black));
        }
    }
}


    然后我们就需要实现自动归位,首先我们需要添加一个圆,如果数字进入圆后,我们松开手指,那么该数字就会自动进入圆心位置。

    首先先让我们画出一个圆,这需要用到canvas.drawCircle(),四个参数分别为

    圆心的X,Y坐标,半径和画笔,在onDraw里添加

    

        mPaint.setStyle(Paint.Style.STROKE);//中空的线
        canvas.drawCircle(mWidth/2,mTextSize+4*mDistance,mDistance/2,mPaint);

    位置半径可以自行确定

    结果如下:

    Android实现自定义滑动选择器_第5张图片

    自动归位的原来很简单,首先是要判断哪个数字离圆心更近,然后通过Timer,每10ms执行一次重新绘制,从而达到自动回到圆心的效果,而这都需要在上面的onTouchEvent中的ACTION_UP条件下执行。

    首先我们将数字都存到一个mData集合中,在init()方法中初始化数据。添加一个int属性mSelected,代表圆心数字在集合中的位置,在init()中初始化为3。

    然后我们需要将绘制数字分成两个方法 drawData()和drawOtherData()。drawData()绘制圆心的数字,,drawOtherData()绘制上下的数字。为了便于直观的感受,圆心的数字将会更大一些。

    

@Override
    protected void onDraw(Canvas canvas) {
        drawData(canvas);
        drawOtherData(canvas,mSelected,DRAW_UP);
        drawOtherData(canvas,mSelected,DRAW_DOWN);
        mPaint.setStyle(Paint.Style.STROKE);//中空的线
        canvas.drawCircle(mWidth/2,mTextSize+3*mDistance,mDistance/2,mPaint);
    }
    private void drawData(Canvas canvas){
        mPaint.setTextSize(mCircleTextSize);
        canvas.drawText(String.valueOf(mData.get(mSelected)), mWidth/2,
                mTextSize+3*mDistance+mMoveLength,mPaint);
    }
    private void drawOtherData(Canvas canvas,int mSelectedPosition,int type){
        mPaint.setTextSize(mTextSize);
        if (type==1){
            for (int i=mSelectedPosition+1;i=0;i--){
                canvas.drawText(String.valueOf(mData.get(i)), mWidth/2,
                        mTextSize+(3+i-mSelectedPosition)*mDistance+mMoveLength,mPaint);
            }
        }
    }


    由于圆是固定的,所以drawData里的Y坐标也是相对固定的,只是会随着mMoveLength上下波动而已,这里的mMoveLength有所不同,具体有什么不同,看下面就知道了。

    然后就是数字是否进入圆的判断。每个数字之间的距离都是mDistance,而圆的半径为mDistance/2。所以,如果当移动的位置mMoveLength超过mDistance就可以算是进入圆心,mSelected也就可以更新了。但是mMoveLength记录的是总的移动距离,只要移动的太远了也不行,所以mMoveLength的计算需要更改一下。

    Android实现自定义滑动选择器_第6张图片

    假设点之间的距离是100,开始selected的点2在圆心位置,然后所有的点一起向上移动70,mMoveLength=-70。这时候点3应该就是selected的点,所以这时候我们也可以认为是所有的点向下移动了30,所以mMoveLength=30。然后我们在代码中实现这段想法。

    

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()){
            case MotionEvent.ACTION_DOWN:
                mLastY=event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                mMoveLength+=event.getRawY()-mLastY;
                if (mMoveLength>=mDistance/2){//手指往下滑
                    if (mSelected>0){
                        mSelected--;
                        mMoveLength=mMoveLength-mDistance;
                    }
                }else if (mMoveLength<=-mDistance/2){//手指往上滑
                    if (mSelected=mDistance/2){
                    mMoveLength=mDistance/2;
                    
                }//已经滑动底部,并且即将出圆,加上textSize是因为坐标从数字的左上角计算
                 else if (mSelected==mData.size()-1&&mMoveLength<=-mDistance/2+mTextSize){
                    mMoveLength=-mDistance/2+mTextSize; 
                }
                mLastY=event.getRawY();
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }

    同时考虑到不能让数字画出范围,所以还加了判断条件。

       效果如下:

    Android实现自定义滑动选择器_第7张图片

    

    至此,全部的代码如下:

public class TestScroller extends View{
    private Paint mPaint;
    private int mWidth;
    private int mHeight;
    private float mTextSize=60;
    private float mCircleTextSize=80;
    private int mDistance=200;
    private float mMoveLength;
    private float mLastY;
    private ListmData;
    private int mSelected;
    private final int DRAW_UP=1;
    private final int DRAW_DOWN=-1;
    public TestScroller(Context context) {
        super(context);
        init();
    }

    public TestScroller(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public TestScroller(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public TestScroller(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth=getMeasuredWidth();
        mHeight=getMeasuredHeight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        drawData(canvas);
        drawOtherData(canvas,mSelected,DRAW_UP);
        drawOtherData(canvas,mSelected,DRAW_DOWN);
        mPaint.setStyle(Paint.Style.STROKE);//中空的线
        canvas.drawCircle(mWidth/2,mTextSize+3*mDistance,mDistance/2,mPaint);
    }
    private void drawData(Canvas canvas){
        mPaint.setTextSize(mCircleTextSize);
        canvas.drawText(String.valueOf(mData.get(mSelected)), mWidth/2,
                mTextSize+3*mDistance+mMoveLength,mPaint);
    }
    private void drawOtherData(Canvas canvas,int mSelectedPosition,int type){
        mPaint.setTextSize(mTextSize);
        if (type==1){
            for (int i=mSelectedPosition+1;i=0;i--){
                canvas.drawText(String.valueOf(mData.get(i)), mWidth/2,
                        mTextSize+(3+i-mSelectedPosition)*mDistance+mMoveLength,mPaint);
            }
        }
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()){
            case MotionEvent.ACTION_DOWN:
                mLastY=event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                mMoveLength+=event.getRawY()-mLastY;
                if (mMoveLength>=mDistance/2){//手指往下滑
                    if (mSelected>0){
                        mSelected--;
                        mMoveLength=mMoveLength-mDistance;
                    }
                }else if (mMoveLength<=-mDistance/2){//手指往上滑
                    if (mSelected=mDistance/2){
                    mMoveLength=mDistance/2;

                }//已经滑动底部,并且即将出圆,加上textSize是因为坐标从数字的左上角计算
                 else if (mSelected==mData.size()-1&&mMoveLength<=-mDistance/2+mTextSize){
                    mMoveLength=-mDistance/2+mTextSize;
                }
                mLastY=event.getRawY();
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }
    private void init(){
        mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setTextSize(mTextSize);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            mPaint.setColor(getContext().getColor(R.color.black));
        }
        mData=new ArrayList<>();
        for (int i=0;i<=10;i++){
            mData.add(i);
        }
        mSelected=3;
    }
}

    现在还剩下最后一个自动复位的功能,前面已经说过了,这个功能是依靠Timer的schedule()方法实现的,该方法可以间隔一定时间重复执行操作。我们则需要通过这个方法,将mMoveLength还原为0就可以实现我们所需要的功能了。

    在这里,你需要用到三个类:Timer、TimerTask和Handle。

    简单地说就是,Timer可以重复执行TimerTask里的操作,而TimerTask里的操作就是Handle要做的事。

    代码如下

    private float mSpead=10;
    private MyTimerTask mTimerTask;
    private Handler mHandler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if (Math.abs(mMoveLength)
 private class MyTimerTask extends TimerTask{
        Handler handler;
        public MyTimerTask(Handler handler) {
            this.handler = handler;
        }
        @Override
        public void run() {
            handler.sendMessage(handler.obtainMessage());
        }
    }
 case MotionEvent.ACTION_UP:
                mTimerTask=new MyTimerTask(mHandler);
                Timer timer=new Timer();
                timer.schedule(mTimerTask,0,10);
                break;

    首先看第一部分代码,我们需要先确定一个自动滑动的速度,也就是每次移动的距离,太大了会给人突兀的感觉,太小了也就滑的太慢了。Handler初始化,第一个if判断是否滑动结束,如果滑动结束,那么mTimerTask如果不为空,就应该取消,一面继续执行。当然,这里肯定是不为空的。

Math.abs(mMoveLength)/mMoveLength

    这个应该不用多说了吧,因为mMoveLength不能确定正负,而这样就一定可以得到相反的符号,然后在乘以mSpead,就可以是mMoveLength靠近0。最后invalid()重绘刷新。

    第二部分代码就是自定义的MyTimerTask类,重写了run()方法,使其执行handler的方法。

    第三部分代码是在onTouchEvent方法中,手指抬起的情况下执行的。timer.schedule(),三个参数分别为:要执行的TimerTask、推迟指定时间后执行以及执行的间隔,这里是10ms。

    最后的效果如下图

    Android实现自定义滑动选择器_第8张图片

    

    最后我们还有一个小地方需要修改,第一部分代码会提示Handler类应该是静态的,这样可能会发生内存泄露,解决的方法很简单,只需要使用弱引用就行了。

        

private MyHandler mHandler=new MyHandler(this);
 private static class MyHandler extends Handler{
        WeakReferenceview;
        public MyHandler(TestScroller view) {
            this.view = new WeakReference<>(view);
        }
        @Override
        public void handleMessage(Message msg) {
            if (view==null)
                return;
            if (Math.abs(view.get().mMoveLength)
    大功告成!

你可能感兴趣的:(优达学习之旅)