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>
时间: 2024-08-11 05:44:38

Android自定义可循环的滚动选择器CycleWheelView 替代TimePicker/NumberPicker/WheelView的相关文章

Android自定义DataTimePicker(日期选择器)

Android自定义DataTimePicker(日期选择器) Android日期时间选择器实现以及自定义大小

Android中实现日期时间选择器(DatePicker和TimePicker)

利用Android应用框架提供的DatePicker(日期选择器)和TimePicker(时间选择器),实现日期时间选择器. Dialog的Content布局文件(date_time_dialog.xml): <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

Android自定义View之区块选择器

效果 先来看下效果吧:我们来分析这个view需要实现哪些效果. 首先它有一个刻度尺代表了时间段(也可以是别的什么),并且可以看到完整的刻度尺是比屏幕宽度大的,因此肯定需要可以左右滑动. 其次,可以有不可选的区域(gif中灰色块)和选中的区域(gif中蓝色块),点击刻度的空白位置出现或者移动选中区域到点击位置. 点击并拖动选中的区域可以移动,当移动到屏幕两边的时候,下层的刻度也能跟着移动. 还可以点击并拖动选中区域右边的白色小圆改变选中区域的大小,同样到达屏幕边界时下层刻度跟着移动. 当选中区域与

Android自定义控件实战——滚动选择器PickerView

转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38513301 手机里设置闹钟需要选择时间,那个选择时间的控件就是滚动选择器,前几天用手机刷了MIUI,发现自带的那个时间选择器效果挺好看的,于是就自己仿写了一个,权当练手.先来看效果: 效果还行吧?实现思路就是自定义一个PickerView,单独滚动的是一个PickerView,显然上图中有分和秒的选择所以在布局里用了两个PickerView.由于这里不涉及到text的点击

Android 自定义RecyclerView 实现真正的Gallery效果

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38173061 ,本文出自:[张鸿洋的博客] 上一篇博客我使用自定义HorizontalScrollView写了一个具有HorizontalScrollView效果和ViewPager特性的横向图片轮播,详见:Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果.其实制作横向滚动的不得不说另一个控件,就是Google

Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38140505 自从Gallery被谷歌废弃以后,Google推荐使用ViewPager和HorizontalScrollView来实现Gallery的效果.的确HorizontalScrollView可以实现Gallery的效果,但是HorizontalScrollView存在一个很大的问题,如果你仅是用来展示少量的图片,应该是没问题的,但是如果我希望HorizontalScr

android 自定义

初级: 1.Android自定义View之一:初探实例 ——> onDraw 2.getwidth和getmeasuredwidth的区别以及两者的使用场景 3.Android 自定义View (一)——>onMesure  MeasureSpec 4.Android LayoutInflater原理分析,带你一步步深入了解View(一) 5.Android视图绘制流程完全解析,带你一步步深入了解View(二) 6.Android视图状态及重绘流程分析,带你一步步深入了解View(三) 7.A

android自定义日历

前几天闲来无事,变想做一些小工具玩玩.花了一天多的时间,弄出一个简单日历的View.分为月份模式和星期模式.滚动查看,先上图看看: 上面的是显示的是月份的模式.下面是星期的模式: 一个很简单的自定义View,然后通过Viewpager的OnpageChangeListener进行刷新View的数据.Viewpager通过轮回使用View.我默认设置是5个.可以左右无限切换. 下面是自定义CalendarView: package com.example.calendar; import andr

Android 自定义 ViewPager 打造千变万化的图片切换效果

Android 自定义 ViewPager 打造千变万化的图片切换效果 标签: Android自定义ViewPagerJazzyViewPager 目录(?)[+] 转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38026503 记 得第一次见到ViewPager这个控件,瞬间爱不释手,做东西的主界面通通ViewPager,以及图片切换也抛弃了ImageSwitch之类的,开 始让ViewPager来做.时间长了,ViewPa