Android自定义WheelView

Posted z8z87878

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义WheelView相关的知识,希望对你有一定的参考价值。

没人可以预见未来,珍惜现在
看项目新原型,好像要用到时间选择控件,我看八成要自定义是吧,然后就去网上找了一点,说用这个WheelView做比较好,还能做什么省市县的。去github看了会,额,感觉可能为了功能强还是什么,感觉代码好多,四五百行,六七百行之类的。反正有时间,还是自己写一个吧。有问题自己也好改先看看效果

这里的WheelView继承的是view,我的思路是,数据和线自己画,然后再监听触摸事件,根据滑动距离改变数据和单行数据的高度,这里弄了个类来记录一行的条目

 private static class ItemString

        private String str; //存储要显示的数据
        private int index;  //存储显示的索引
        private float height; //存储显示的高度位置

        public ItemString() 
        

        public ItemString(String str, int index,float height) 
            this.str = str;
            this.index = index;
            this.height = height;
        

        public String getStr() 
            return str;
        

        public void setStr(String str) 
            this.str = str;
        

        public int getIndex() 
            return index;
        

        public void setIndex(int index) 
            this.index = index;
        

        public float getHeight() 
            return height;
        

        public void setHeight(float height) 
            this.height = height;
        
    

先来看看不能滑动时什么样子


/**
 * Created by liaoyalong on 2016-12-28.
 */

public class WheelView extends View

    private int textSize = dp2px(15); //普通字体大小
    private int selectTextSize = dp2px(24);  //选中字体的大小
    private Paint normalPaint;   //普通字体画笔
    private Paint selectPaint;  //选中字体画笔
    private Paint linePaint;  //线画笔
    private int height = 0;    //控件的高度
    private int width = 0;   //控件宽度
    private int textHeight = 0;  //普通字体高度
    private int selectTextHeight = 0;  //选中字体高度
    private List<String> mStringList;   //存储传进来的数据
    private List<ItemString> itemList = new ArrayList<>();   //存储显示的条目,默认显示3行,既默认存储5条。第一行和最后一行不可见
    private List<Float> itemHeight = new ArrayList<>();   //存储每个条目所在控件的高度
    private float selectHeight;  //存储选中条目所在控件的高度

    private int totalText = 0;   //总共有多少条数据
    private int currentIndex = Integer.MAX_VALUE;  //当前选中数据的索引值
    private int displayCount = 3;  //默认显示3行
    private float lineHeight = 0;  //行高
    private int mCenterIndex;        //中间,既选中行的索引

    public WheelView(Context context) 
        this(context,null);
    

    public WheelView(Context context, AttributeSet attrs) 
        this(context, attrs,0);
    

    public WheelView(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);

        normalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        normalPaint.setTextAlign(Paint.Align.CENTER);
        normalPaint.setTextSize(textSize);
        normalPaint.setColor(Color.GRAY);
        normalPaint.setTypeface(Typeface.SERIF);
        normalPaint.setTextSkewX(-0.3f);               //字体倾斜度调整

        selectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        selectPaint.setTextAlign(Paint.Align.CENTER);
        selectPaint.setTextSize(selectTextSize);
        selectPaint.setColor(Color.RED);

        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setColor(Color.BLUE);

        //所有需要自己修改的字体效果都可以自己加
        /*----------------------------如有需要,自行定义attr增加属性可控性--------------------------------*/
    

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)  //当测量完成后,获得我们想要的不变的控件宽高
        super.onSizeChanged(w, h, oldw, oldh);

        height = getHeight();
        width  = getWidth();
        lineHeight = height/3.0f;

        Rect bound = new Rect();
        normalPaint.getTextBounds("123",0,1,bound);
        textHeight = bound.height();

        selectPaint.getTextBounds("123",0,1,bound);
        selectTextHeight = bound.height();
    

    /**保证传入的数据大于等于2,注意要在WheelView的onSizeChanged执行完后调用,因为这里用到了测量后的宽高,所以可mWheelView.getViewTreeObserver().addOnGlobalLayoutListener
     * @param list
     */
    public void setListText(List<String> list)
        mStringList = list;

        totalText = list.size(); //总共有多少条数据
        currentIndex = currentIndex/2 - currentIndex/2 % (totalText); //当前中间的那条,这样为了循环移动

        mCenterIndex = (displayCount+2)/2;  //中间的是第几行
        itemList.clear();
        itemHeight.clear();
        for (int i = 0; i < displayCount + 2; i++)    //存储每条条目的初始高度,和条目
            float hei = (lineHeight * (i-1) + lineHeight/2.0f + textHeight/2.0f);
            itemHeight.add(hei);
            int index = currentIndex + i -mCenterIndex;
            String text = list.get(index % totalText);         //取%防止越界,为了循环移动
            itemList.add( new ItemString(text, index, hei) );

        

        selectHeight = height/2.0f + selectTextHeight/2.0f;       //选中条目所在控件的高度位置
        Float f = itemHeight.get(mCenterIndex);
        f = selectHeight;

        itemList.get(mCenterIndex).setHeight(selectHeight);

        postInvalidate();   //重新绘制
    

    @Override
    protected void onDraw(Canvas canvas) 

        super.onDraw(canvas); //画view的背景色

        for (int i = 1; i < displayCount; i++)  //画线
            canvas.drawLine(0,lineHeight * i,width,lineHeight * i,linePaint);
        


        for (int i = 0; i < itemList.size(); i++) 

            ItemString itemString = itemList.get(i);
            if (i != mCenterIndex)                              //如果不是选中行
                canvas.drawText(itemString.getStr(),width/2.0f,itemString.getHeight(),normalPaint);
            else 
                canvas.drawText(itemString.getStr(),width/2.0f,itemString.getHeight(),selectPaint);//选中行换画笔
            
        

    

不能滑动,静态的花点时间,耐心点就可以画出来了,看看怎么滑动判断的吧

private float downY;  //按下时的位置
    private float delY;  //移动距离
    @Override
    public boolean onTouchEvent(MotionEvent event) 

        switch (event.getAction())

            case MotionEvent.ACTION_DOWN:

                downY = event.getY();
                break;

            case MotionEvent.ACTION_MOVE:
                delY = event.getY() - downY;
                if (nextLine(delY))
                    exchangeLine(true);

                else if (preLine(delY))

                    exchangeLine(false);
                else 

                    for (int i = 0; i < itemList.size(); i++)   //不换行时根据原来的高度加上滑动距离改变位置
                        itemList.get(i).setHeight(itemHeight.get(i)+delY);
                    
                

                break;

            case MotionEvent.ACTION_UP:

                downY = 0;
                for (int i = 0; i < itemList.size(); i++) 
                    itemList.get(i).setHeight(itemHeight.get(i));
                
                break;
        

        //设置滑动透明度变化,需要自行调整
        int alphy = (int) (255 - 330 * Math.abs(itemList.get(0).getHeight() - itemHeight.get(0)) / (lineHeight/2 + selectHeight/2));
        normalPaint.setAlpha(alphy);
        selectPaint.setAlpha(alphy);
        postInvalidate();
        return super.onTouchEvent(event);
    

    /**交换行
     * @param next
     */
    private void exchangeLine(boolean next)

        if(next)
            itemList.remove(0);  //移除第一行
            int nextIndex = (itemList.get(itemList.size()-1).getIndex()+1);
            itemList.add(new ItemString(mStringList.get(nextIndex % totalText),nextIndex,0));  //添加新数据到最后一行(最后一行和第一行不滑动都看不到)
            for (int i = 0; i < itemList.size(); i++) 
                itemList.get(i).setHeight(itemHeight.get(i));     //重新设置高度
            
        else 

            itemList.remove(itemList.size()-1);                  //移除最后一行
            int preIndex = (itemList.get(0).getIndex()-1);
            itemList.add(0,new ItemString(mStringList.get(preIndex % totalText),preIndex,0));   //添加新数据到第一行
            for (int i = 0; i < itemList.size(); i++) 
                itemList.get(i).setHeight(itemHeight.get(i));     //重新设置高度
            
        
    


    /**是否上一行
     * @param delY
     * @return
     */
    private boolean preLine(float delY)

        float move = lineHeight/2 + selectTextHeight/2;
        if (delY > move)
            downY += move;
            currentIndex -= 1;
            return true;
        

        return false;
    

    /**是否下一行
     * @param delY
     * @return
     */
    private boolean nextLine(float delY)

        float move = lineHeight/2 + selectTextHeight/2;
        if (delY < 0 && delY + move < 0)
            downY -= move;
            currentIndex += 1;
            return true;
        

        return false;
    

    public int getSelectIndex()
        return currentIndex % totalText;
    

好吧,我承认,下班了,我想走了,自己看注释吧,我觉得我注释挺详细的。等下贴完整代码在评论区

package com.example.wheelview.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by liaoyalong on 2016-12-28.
 */

public class WheelView extends View

    private int textSize = dp2px(15); //普通字体大小
    private int selectTextSize = dp2px(24);  //选中字体的大小
    private Paint normalPaint;   //普通字体画笔
    private Paint selectPaint;  //选中字体画笔
    private Paint linePaint;  //线画笔
    private int height = 0;    //控件的高度
    private int width = 0;   //控件宽度
    private int textHeight = 0;  //普通字体高度
    private int selectTextHeight = 0;  //选中字体高度
    private List<String> mStringList;   //存储传进来的数据
    private List<ItemString> itemList = new ArrayList<>();   //存储显示的条目,默认显示3行,既默认存储5条。第一行和最后一行不可见
    private List<Float> itemHeight = new ArrayList<>();   //存储每个条目所在控件的高度
    private float selectHeight;  //存储选中条目所在控件的高度

    private int totalText = 0;   //总共有多少条数据
    private int currentIndex = Integer.MAX_VALUE;  //当前选中数据的索引值
    private int displayCount = 3;  //默认显示3行
    private float lineHeight = 0;  //行高
    private int mCenterIndex;        //中间,既选中行的索引

    public WheelView(Context context) 
        this(context,null);
    

    public WheelView(Context context, AttributeSet attrs) 
        this(context, attrs,0);
    

    public WheelView(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);

        normalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        normalPaint.setTextAlign(Paint.Align.CENTER);
        normalPaint.setTextSize(textSize);
        normalPaint.setColor(Color.GRAY);
        normalPaint.setTypeface(Typeface.SERIF);
        normalPaint.setTextSkewX(-0.3f);               //字体倾斜度调整

        selectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        selectPaint.setTextAlign(Paint.Align.CENTER);
        selectPaint.setTextSize(selectTextSize);
        selectPaint.setColor(Color.RED);

        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setColor(Color.BLUE);

        //所有需要自己修改的字体效果都可以自己加
        /*----------------------------如有需要,自行定义attr增加属性可控性--------------------------------*/
    

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)  //当测量完成后,获得我们想要的不变的控件宽高
        super.onSizeChanged(w, h, oldw, oldh);

        height = getHeight();
        width  = getWidth();
        lineHeight = height/3.0f;

        Rect bound = new Rect();
        normalPaint.getTextBounds("123",0,1,bound);
        textHeight = bound.height();

        selectPaint.getTextBounds("123",0,1,bound);
        selectTextHeight = bound.height();
    

    /**保证传入的数据大于等于2,注意要在WheelView的onSizeChanged执行完后调用,因为这里用到了测量后的宽高,所以可mWheelView.getViewTreeObserver().addOnGlobalLayoutListener
     * @param list
     */
    public void setListText(List<String> list)
        mStringList = list;

        totalText = list.size(); //总共有多少条数据
        currentIndex = currentIndex/2 - currentIndex/2 % (totalText); //当前中间的那条,这样为了循环移动

        mCenterIndex = (displayCount+2)/2;  //中间的是第几行
        itemList.clear();
        itemHeight.clear();
        for (int i = 0; i < displayCount + 2; i++)    //存储每条条目的初始高度,和条目
            float hei = (lineHeight * (i-1) + lineHeight/2.0f + textHeight/2.0f);
            itemHeight.add(hei);
            int index = currentIndex + i -mCenterIndex;
            String text = list.get(index % totalText);         //取%防止越界,为了循环移动
            itemList.add( new ItemString(text, index, hei) );

        

        selectHeight = height/2.0f + selectTextHeight/2.0f;       //选中条目所在控件的高度位置
        Float f = itemHeight.get(mCenterIndex);
        f = selectHeight;

        itemList.get(mCenterIndex).setHeight(selectHeight);

        postInvalidate();   //重新绘制
    

    @Override
    protected void onDraw(Canvas canvas) 

        super.onDraw(canvas); //画view的背景色

        for (int i = 1; i < displayCount; i++)  //画线
            canvas.drawLine(0,lineHeight * i,width,lineHeight * i,linePaint);
        


        for (int i = 0; i < itemList.size(); i++) 

            ItemString itemString = itemList.get(i);
            if (i != mCenterIndex)                              //如果不是选中行
                canvas.drawText(itemString.getStr(),width/2.0f,itemString.getHeight(),normalPaint);
            else 
                canvas.drawText(itemString.getStr(),width/2.0f,itemString.getHeight(),selectPaint);//选中行换画笔
            
        

    

    private float downY;  //按下时的位置
    private float delY;  //移动距离
    @Override
    public boolean onTouchEvent(MotionEvent event) 

        switch (event.getAction())

            case MotionEvent.ACTION_DOWN:

                downY = event.getY();
                break;

            case MotionEvent.ACTION_MOVE:
                delY = event.getY() - downY;
                if (nextLine(delY))
                    exchangeLine(true);

                else if (preLine(delY))

                    exchangeLine(false);
                else 

                    for (int i = 0; i < itemList.size(); i++)   //不换行时根据原来的高度加上滑动距离改变位置
                        itemList.get(i).setHeight(itemHeight.get(i)+delY);
                    
                

                break;

            case MotionEvent.ACTION_UP:

                downY = 0;
                for (int i = 0; i < itemList.size(); i++) 
                    itemList.get(i).setHeight(itemHeight.get(i));
                
                break;
        

        //设置滑动透明度变化,需要自行调整
        int alphy = (int) (255 - 330 * Math.abs(itemList.get(0).getHeight() - itemHeight.get(0)) / (lineHeight/2 + selectHeight/2));
        normalPaint.setAlpha(alphy);
        selectPaint.setAlpha(alphy);
        postInvalidate();
        return super.onTouchEvent(event);
    

    /**交换行
     * @param next
     */
    private void exchangeLine(boolean next)

        if(next)
            itemList.remove(0);  //移除第一行
            int nextIndex = (itemList.get(itemList.size()-1).getIndex()+1);
            itemList.add(new ItemString(mStringList.get(nextIndex % totalText),nextIndex,0));  //添加新数据到最后一行(最后一行和第一行不滑动都看不到)
            for (int i = 0; i < itemList.size(); i++) 
                itemList.get(i).setHeight(itemHeight.get(i));     //重新设置高度
            
        else 

            itemList.remove(itemList.size()-1);                  //移除最后一行
            int preIndex = (itemList.get(0).getIndex()-1);
            itemList.add(0,new ItemString(mStringList.get(preIndex % totalText),preIndex,0));   //添加新数据到第一行
            for (int i = 0; i < itemList.size(); i++) 
                itemList.get(i).setHeight(itemHeight.get(i));     //重新设置高度
            
        
    


    /**是否上一行
     * @param delY
     * @return
     */
    private boolean preLine(float delY)

        float move = lineHeight/2 + selectTextHeight/2;
        if (delY > move)
            downY += move;
            currentIndex -= 1;
            return true;
        

        return false;
    

    /**是否下一行
     * @param delY
     * @return
     */
    private boolean nextLine(float delY)

        float move = lineHeight/2 + selectTextHeight/2;
        if (delY < 0 && delY + move < 0)
            downY -= move;
            currentIndex += 1;
            return true;
        

        return false;
    

    public int getSelectIndex()
        return currentIndex % totalText;
    

    private static class ItemString

        private String str; //存储要显示的数据
        private int index;  //存储显示的索引
        private float height; //存储显示的高度位置

        public ItemString() 
        

        public ItemString(String str, int index,float height) 
            this.str = str;
            this.index = index;
            this.height = height;
        

        public String getStr() 
            return str;
        

        public void setStr(String str) 
            this.str = str;
        

        public int getIndex() 
            return index;
        

        public void setIndex(int index) 
            this.index = index;
        

        public float getHeight() 
            return height;
        

        public void setHeight(float height) 
            this.height = height;
        
    

    private int dp2px(int dp)

        return (int) (getResources().getDisplayMetrics().density * dp);
    

activity

public class MainActivity extends AppCompatActivity 


    private WheelView mWheelView;
    String[] text = "0","1","2","3","4","5","6","7","8","9","10";
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mWheelView = (WheelView) findViewById(R.id.wheel);

        mWheelView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() 
            @Override
            public void onGlobalLayout() 
                mWheelView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                mWheelView.setListText(Arrays.asList(text));
            
        );


        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                int index = mWheelView.getSelectIndex();
                Toast.makeText(MainActivity.this,"当前索引为"+index+",对应的值为"+text[index],Toast.LENGTH_SHORT).show();
            
        );
    

以上是关于Android自定义WheelView的主要内容,如果未能解决你的问题,请参考以下文章

Android自定义WheelView

android 加速度传感器妙用与自定义View

如何使用 Android 中的加速度计测量手机在 XY 平面上的倾斜度

Android自己定义实现循环滚轮控件WheelView

Android 自定义倾斜字体

菜鸟都应该知道的倾斜摄影测量知识