当前位置: 代码迷 >> Android >> Android自定义可循环的滚动选择器CycleWheelView 顶替TimePicker/NumberPicker/WheelView
  详细解决方案

Android自定义可循环的滚动选择器CycleWheelView 顶替TimePicker/NumberPicker/WheelView

热度:59   发布时间:2016-04-28 00:03:56.0
Android自定义可循环的滚动选择器CycleWheelView 替代TimePicker/NumberPicker/WheelView

最近碰到个项目要使用到滚动选择器,原生的NumberPicker可定制性太差,不大符合UI要求。

 

网上开源的WheelView是用ScrollView写的,不能循环滚动,而且当数据量很大时要加载的Item太多,性能非常低。

 

然后,还是自己写一个比较靠谱,用的是ListView实现的。写完自己体验了一下,性能不错,再大的数据也不怕了。

 

感觉不错,重新封装了一下,提供了一些接口可以直接按照自己的需求定制,调用方法在MainActivity中。

 

不多说了,直接上代码:

 

CycleWheelView.java:

/** * Copyright (C) 2015 * * CycleWheelView.java * * Description:  * * Author: Liao Longhui  * * Ver 1.0, 2015-07-15, Liao Longhui, Create file */package com.example.wheelviewdemo;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.ColorFilter;import android.graphics.Paint;import android.graphics.drawable.Drawable;import android.os.Handler;import android.util.AttributeSet;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.AbsListView;import android.widget.BaseAdapter;import android.widget.ListView;import android.widget.TextView;import java.util.ArrayList;import java.util.List;/** * 可循环滚动的选择器 * @author Liao Longhui * */public class CycleWheelView extends ListView {    public static final String TAG = CycleWheelView.class.getSimpleName();    private static final int COLOR_DIVIDER_DEFALUT = Color.parseColor("#747474");    private static final int HEIGHT_DIVIDER_DEFAULT = 2;    private static final int COLOR_SOLID_DEFAULT = Color.parseColor("#3e4043");    private static final int COLOR_SOLID_SELET_DEFAULT = Color.parseColor("#323335");    private static final int WHEEL_SIZE_DEFAULT = 3;    private Handler mHandler;    private CycleWheelViewAdapter mAdapter;    /**     * Labels     */    private List<String> mLabels;    /**     * Color Of Selected Label     */    private int mLabelSelectColor = Color.WHITE;    /**     * Color Of Unselected Label     */    private int mLabelColor = Color.GRAY;    /**     * Gradual Alph     */    private float mAlphaGradual = 0.7f;    /**     * Color Of Divider     */    private int dividerColor = COLOR_DIVIDER_DEFALUT;    /**     * Height Of Divider     */    private int dividerHeight = HEIGHT_DIVIDER_DEFAULT;    /**     * Color of Selected Solid     */    private int seletedSolidColor = COLOR_SOLID_SELET_DEFAULT;    /**     * Color of Unselected Solid     */    private int solidColor = COLOR_SOLID_DEFAULT;    /**     * Size Of Wheel , it should be odd number like 3 or greater     */    private int mWheelSize = WHEEL_SIZE_DEFAULT;    /**     * res Id of Wheel Item Layout     */    private int mItemLayoutId;    /**     * res Id of Label TextView     */    private int mItemLabelTvId;    /**     * Height of Wheel Item     */    private int mItemHeight;    private boolean cylceEnable;    private int mCurrentPositon;    private WheelItemSelectedListener mItemSelectedListener;    public CycleWheelView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);    }    public CycleWheelView(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    public CycleWheelView(Context context) {        super(context);    }    private void init() {        mHandler = new Handler();        mItemLayoutId = R.layout.item_cyclewheel;        mItemLabelTvId = R.id.tv_label_item_wheel;        mAdapter = new CycleWheelViewAdapter();        setVerticalScrollBarEnabled(false);        setScrollingCacheEnabled(false);        setCacheColorHint(Color.TRANSPARENT);        setFadingEdgeLength(0);        setOverScrollMode(OVER_SCROLL_NEVER);        setDividerHeight(0);        setAdapter(mAdapter);        setOnScrollListener(new OnScrollListener() {            @Override            public void onScrollStateChanged(AbsListView view, int scrollState) {                if (scrollState == SCROLL_STATE_IDLE) {                    View itemView = getChildAt(0);                    if (itemView != null) {                        float deltaY = itemView.getY();                        if (deltaY == 0) {                            return;                        }                        if (Math.abs(deltaY) < mItemHeight / 2) {                            smoothScrollBy(getDistance(deltaY), 50);                        } else {                            smoothScrollBy(getDistance(mItemHeight + deltaY), 50);                        }                    }                }            }            @Override            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,                    int totalItemCount) {                refreshItems();            }        });    }    private int getDistance(float scrollDistance) {        if (Math.abs(scrollDistance) <= 2) {            return (int) scrollDistance;        } else if (Math.abs(scrollDistance) < 12) {            return scrollDistance > 0 ? 2 : -2;        } else {            return (int) (scrollDistance / 6);        }    }    private void refreshItems() {        int offset = mWheelSize / 2;        int firstPosition = getFirstVisiblePosition();        int position = 0;        if (getChildAt(0) == null) {            return;        }        if (Math.abs(getChildAt(0).getY()) <= mItemHeight / 2) {            position = firstPosition + offset;        } else {            position = firstPosition + offset + 1;        }        if (position == mCurrentPositon) {            return;        }        mCurrentPositon = position;        if (mItemSelectedListener != null) {            mItemSelectedListener.onItemSelected(getSelection(), getSelectLabel());        }        resetItems(firstPosition, position, offset);    }    private void resetItems(int firstPosition, int position, int offset){        for (int i = position - offset - 1; i < position + offset + 1; i++) {            View itemView = getChildAt(i - firstPosition);            if (itemView == null) {                continue;            }            TextView labelTv = (TextView) itemView.findViewById(mItemLabelTvId);            if (position == i) {                labelTv.setTextColor(mLabelSelectColor);                itemView.setAlpha(1f);            } else {                labelTv.setTextColor(mLabelColor);                int delta = Math.abs(i - position);                double alpha = Math.pow(mAlphaGradual, delta);                itemView.setAlpha((float) alpha);            }        }    }        /**     * 设置滚轮的刻度列表     *      * @param labels     */    public void setLabels(List<String> labels) {        mLabels = labels;        mAdapter.setData(mLabels);        mAdapter.notifyDataSetChanged();        initView();    }    /**     * 设置滚轮滚动监听     *      * @param mItemSelectedListener     */    public void setOnWheelItemSelectedListener(WheelItemSelectedListener mItemSelectedListener) {        this.mItemSelectedListener = mItemSelectedListener;    }    /**     * 获取滚轮的刻度列表     *      * @return     */    public List<String> getLabels() {        return mLabels;    }    /**     * 设置滚轮是否为循环滚动     *      * @param enable true-循环 false-单程     */    public void setCycleEnable(boolean enable) {        if (cylceEnable != enable) {            cylceEnable = enable;            mAdapter.notifyDataSetChanged();            setSelection(getSelection());        }    }    /*     * 滚动到指定位置     */    @Override    public void setSelection(final int position) {        mHandler.post(new Runnable() {            @Override            public void run() {                CycleWheelView.super.setSelection(getPosition(position));            }        });    }    private int getPosition(int positon) {        if (mLabels == null || mLabels.size() == 0) {            return 0;        }        if (cylceEnable) {            int d = Integer.MAX_VALUE / 2 / mLabels.size();            return positon + d * mLabels.size();        }        return positon;    }    /**     * 获取当前滚轮位置     *      * @return     */    public int getSelection() {        if (mCurrentPositon == 0) {            mCurrentPositon = mWheelSize / 2;        }        return (mCurrentPositon - mWheelSize / 2) % mLabels.size();    }    /**     * 获取当前滚轮位置的刻度     *      * @return     */    public String getSelectLabel() {        int position = getSelection();        position = position < 0 ? 0 : position;        try {            return mLabels.get(position);        } catch (Exception e) {            return "";        }    }    /**     * 如果需要自定义滚轮每个Item,调用此方法设置自定义Item布局,自定义布局中需要一个TextView来显示滚轮刻度     *      * @param itemResId 布局文件Id     * @param labelTvId 刻度TextView的资源Id     */    public void setWheelItemLayout(int itemResId, int labelTvId) {        mItemLayoutId = itemResId;        mItemLabelTvId = labelTvId;        mAdapter = new CycleWheelViewAdapter();        mAdapter.setData(mLabels);        setAdapter(mAdapter);        initView();    }    /**     * 设置未选中刻度文字颜色     *      * @param labelColor     */    public void setLabelColor(int labelColor) {        this.mLabelColor = labelColor;        resetItems(getFirstVisiblePosition(), mCurrentPositon, mWheelSize/2);    }    /**     * 设置选中刻度文字颜色     *      * @param labelSelectColor     */    public void setLabelSelectColor(int labelSelectColor) {        this.mLabelSelectColor = labelSelectColor;        resetItems(getFirstVisiblePosition(), mCurrentPositon, mWheelSize/2);    }    /**     * 设置滚轮刻度透明渐变值     *      * @param alphaGradual     */    public void setAlphaGradual(float alphaGradual) {        this.mAlphaGradual = alphaGradual;        resetItems(getFirstVisiblePosition(), mCurrentPositon, mWheelSize/2);    }    /**     * 设置滚轮可显示的刻度数量,必须为奇数,且大于等于3     *      * @param wheelSize     * @throws CycleWheelViewException 滚轮数量错误     */    public void setWheelSize(int wheelSize) throws CycleWheelViewException  {        if (wheelSize < 3 || wheelSize % 2 != 1) {            throw new CycleWheelViewException("Wheel Size Error , Must Be 3,5,7,9...");        } else {            mWheelSize = wheelSize;            initView();        }    }        /**     * 设置块的颜色     * @param unselectedSolidColor 未选中的块的颜色     * @param selectedSolidColor 选中的块的颜色     */    public void setSolid(int unselectedSolidColor, int selectedSolidColor){        this.solidColor = unselectedSolidColor;        this.seletedSolidColor = selectedSolidColor;        initView();    }        /**     * 设置分割线样式     * @param dividerColor  分割线颜色     * @param dividerHeight 分割线高度(px)     */    public void setDivider(int dividerColor, int dividerHeight){        this.dividerColor = dividerColor;        this.dividerHeight = dividerHeight;    }    @SuppressWarnings("deprecation")    private void initView() {        mItemHeight = measureHeight();        ViewGroup.LayoutParams lp = getLayoutParams();        lp.height = mItemHeight * mWheelSize;        mAdapter.setData(mLabels);        mAdapter.notifyDataSetChanged();        Drawable backgroud = new Drawable() {            @Override            public void draw(Canvas canvas) {                int viewWidth = getWidth();                Paint dividerPaint = new Paint();                dividerPaint.setColor(dividerColor);                dividerPaint.setStrokeWidth(dividerHeight);                Paint seletedSolidPaint = new Paint();                seletedSolidPaint.setColor(seletedSolidColor);                Paint solidPaint = new Paint();                solidPaint.setColor(solidColor);                canvas.drawRect(0, 0, viewWidth, mItemHeight * (mWheelSize / 2), solidPaint);                canvas.drawRect(0, mItemHeight * (mWheelSize / 2 + 1), viewWidth, mItemHeight                        * (mWheelSize), solidPaint);                canvas.drawRect(0, mItemHeight * (mWheelSize / 2), viewWidth, mItemHeight                        * (mWheelSize / 2 + 1), seletedSolidPaint);                canvas.drawLine(0, mItemHeight * (mWheelSize / 2), viewWidth, mItemHeight                        * (mWheelSize / 2), dividerPaint);                canvas.drawLine(0, mItemHeight * (mWheelSize / 2 + 1), viewWidth, mItemHeight                        * (mWheelSize / 2 + 1), dividerPaint);            }            @Override            public void setAlpha(int alpha) {            }            @Override            public void setColorFilter(ColorFilter cf) {            }            @Override            public int getOpacity() {                return 0;            }        };        setBackgroundDrawable(backgroud);    }    private int measureHeight() {        View itemView = LayoutInflater.from(getContext()).inflate(mItemLayoutId, null);        itemView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,                ViewGroup.LayoutParams.WRAP_CONTENT));        int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);        int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);        itemView.measure(w, h);        int height = itemView.getMeasuredHeight();        // int width = view.getMeasuredWidth();        return height;    }    public interface WheelItemSelectedListener {        public void onItemSelected(int position, String label);    }    public class CycleWheelViewException extends Exception {        private static final long serialVersionUID = 1L;        public CycleWheelViewException(String detailMessage) {            super(detailMessage);        }    }    public class CycleWheelViewAdapter extends BaseAdapter {        private List<String> mData = new ArrayList<String>();        public void setData(List<String> mWheelLabels) {            mData.clear();            mData.addAll(mWheelLabels);        }        @Override        public int getCount() {            if (cylceEnable) {                return Integer.MAX_VALUE;            }            return mData.size() + mWheelSize - 1;        }        @Override        public Object getItem(int position) {            return "";        }        @Override        public long getItemId(int position) {            return position;        }        @Override        public boolean isEnabled(int position) {            return false;        }        @Override        public View getView(int position, View convertView, ViewGroup parent) {            if (convertView == null) {                convertView = LayoutInflater.from(getContext()).inflate(mItemLayoutId, null);            }            TextView textView = (TextView) convertView.findViewById(mItemLabelTvId);            if (position < mWheelSize / 2                    || (!cylceEnable && position >= mData.size() + mWheelSize / 2)) {                textView.setText("");                convertView.setVisibility(View.INVISIBLE);            } else {                textView.setText(mData.get((position - mWheelSize / 2) % mData.size()));                convertView.setVisibility(View.VISIBLE);            }            return convertView;        }    }}

 

MainActivity.java:

public class MainActivity extends Activity {    private CycleWheelView cycleWheelView0,cycleWheelView1, cycleWheelView2;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        cycleWheelView0 = (CycleWheelView) findViewById(R.id.cycleWheelView);        List<String> labels = new ArrayList<>();        for (int i = 0; i < 12; i++) {            labels.add("" + i);        }        cycleWheelView0.setLabels(labels);        cycleWheelView0.setAlphaGradual(0.5f);        cycleWheelView0.setOnWheelItemSelectedListener(new WheelItemSelectedListener() {            @Override            public void onItemSelected(int position, String label) {                Log.d("test", label);            }        });                cycleWheelView1 = (CycleWheelView) findViewById(R.id.cycleWheelView1);        List<String> labels1 = new ArrayList<>();        for (int i = 0; i < 24; i++) {            labels1.add("" + i);        }        cycleWheelView1.setLabels(labels1);        try {            cycleWheelView1.setWheelSize(5);        } catch (CycleWheelViewException e) {            e.printStackTrace();        }        cycleWheelView1.setSelection(2);        cycleWheelView1.setWheelItemLayout(R.layout.item_cyclewheel_custom, R.id.tv_label_item_wheel_custom);        cycleWheelView1.setOnWheelItemSelectedListener(new WheelItemSelectedListener() {            @Override            public void onItemSelected(int position, String label) {                Log.d("test", label);            }        });        cycleWheelView2 = (CycleWheelView) findViewById(R.id.cycleWheelView2);        List<String> labels2 = new ArrayList<>();        for (int i = 0; i < 60; i++) {            labels2.add("" + i);        }        cycleWheelView2.setLabels(labels2);        try {            cycleWheelView2.setWheelSize(7);        } catch (CycleWheelViewException e) {            e.printStackTrace();        }        cycleWheelView2.setCycleEnable(true);        cycleWheelView2.setSelection(30);        cycleWheelView2.setAlphaGradual(0.6f);        cycleWheelView2.setDivider(Color.parseColor("#abcdef"), 2);        cycleWheelView2.setSolid(Color.WHITE,Color.WHITE);        cycleWheelView2.setLabelColor(Color.BLUE);        cycleWheelView2.setLabelSelectColor(Color.RED);        cycleWheelView2.setOnWheelItemSelectedListener(new WheelItemSelectedListener() {            @Override            public void onItemSelected(int position, String label) {                Log.d("test", label);            }        });    }}

Item_cyclewheel.xml:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:padding="16dp"    android:background="@android:color/transparent" >    <TextView        android:id="@+id/tv_label_item_wheel"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:textSize="20sp"        android:singleLine="true"        android:layout_centerHorizontal="true"        android:layout_centerVertical="true" /></RelativeLayout>

Item_cyclewheel_custom.xml:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:padding="16dp"    android:background="@android:color/transparent" >    <TextView        android:id="@+id/tv_label_item_wheel_custom"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:singleLine="true"        android:layout_alignParentLeft="true"        android:layout_centerVertical="true" />    <ImageView        android:layout_width="25dp"        android:layout_height="25dp"        android:layout_centerVertical="true"        android:layout_alignParentRight="true"        android:src="@drawable/ic_launcher" /></RelativeLayout>

activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="horizontal" >    <com.example.wheelviewdemo.CycleWheelView        android:id="@+id/cycleWheelView"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_weight="1" >    </com.example.wheelviewdemo.CycleWheelView>        <com.example.wheelviewdemo.CycleWheelView        android:id="@+id/cycleWheelView1"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_weight="1" >    </com.example.wheelviewdemo.CycleWheelView>    <com.example.wheelviewdemo.CycleWheelView        android:id="@+id/cycleWheelView2"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_weight="1" >    </com.example.wheelviewdemo.CycleWheelView>    </LinearLayout>

 

  相关解决方案