Android自定義可循環(huán)的滾動(dòng)選擇器CycleWheelView
最近碰到個(gè)項(xiàng)目要使用到滾動(dòng)選擇器,原生的NumberPicker可定制性太差,不大符合UI要求。
網(wǎng)上開(kāi)源的WheelView是用ScrollView寫(xiě)的,不能循環(huán)滾動(dòng),而且當(dāng)數(shù)據(jù)量很大時(shí)要加載的Item太多,性能非常低。
然后,還是自己寫(xiě)一個(gè)比較靠譜,用的是ListView實(shí)現(xiàn)的。寫(xiě)完自己體驗(yàn)了一下,性能不錯(cuò),再大的數(shù)據(jù)也不怕了。
感覺(jué)不錯(cuò),重新封裝了一下,提供了一些接口可以直接按照自己的需求定制,調(diào)用方法在MainActivity中。
補(bǔ)個(gè)圖片:

不多說(shuō)了,直接上代碼:
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;
/**
* 可循環(huán)滾動(dòng)的選擇器
* @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);
}
}
}
/**
* 設(shè)置滾輪的刻度列表
*
* @param labels
*/
public void setLabels(List<String> labels) {
mLabels = labels;
mAdapter.setData(mLabels);
mAdapter.notifyDataSetChanged();
initView();
}
/**
* 設(shè)置滾輪滾動(dòng)監(jiān)聽(tīng)
*
* @param mItemSelectedListener
*/
public void setOnWheelItemSelectedListener(WheelItemSelectedListener mItemSelectedListener) {
this.mItemSelectedListener = mItemSelectedListener;
}
/**
* 獲取滾輪的刻度列表
*
* @return
*/
public List<String> getLabels() {
return mLabels;
}
/**
* 設(shè)置滾輪是否為循環(huán)滾動(dòng)
*
* @param enable true-循環(huán) false-單程
*/
public void setCycleEnable(boolean enable) {
if (cylceEnable != enable) {
cylceEnable = enable;
mAdapter.notifyDataSetChanged();
setSelection(getSelection());
}
}
/*
* 滾動(dòng)到指定位置
*/
@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;
}
/**
* 獲取當(dāng)前滾輪位置
*
* @return
*/
public int getSelection() {
if (mCurrentPositon == 0) {
mCurrentPositon = mWheelSize / 2;
}
return (mCurrentPositon - mWheelSize / 2) % mLabels.size();
}
/**
* 獲取當(dāng)前滾輪位置的刻度
*
* @return
*/
public String getSelectLabel() {
int position = getSelection();
position = position < 0 ? 0 : position;
try {
return mLabels.get(position);
} catch (Exception e) {
return "";
}
}
/**
* 如果需要自定義滾輪每個(gè)Item,調(diào)用此方法設(shè)置自定義Item布局,自定義布局中需要一個(gè)TextView來(lái)顯示滾輪刻度
*
* @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();
}
/**
* 設(shè)置未選中刻度文字顏色
*
* @param labelColor
*/
public void setLabelColor(int labelColor) {
this.mLabelColor = labelColor;
resetItems(getFirstVisiblePosition(), mCurrentPositon, mWheelSize/2);
}
/**
* 設(shè)置選中刻度文字顏色
*
* @param labelSelectColor
*/
public void setLabelSelectColor(int labelSelectColor) {
this.mLabelSelectColor = labelSelectColor;
resetItems(getFirstVisiblePosition(), mCurrentPositon, mWheelSize/2);
}
/**
* 設(shè)置滾輪刻度透明漸變值
*
* @param alphaGradual
*/
public void setAlphaGradual(float alphaGradual) {
this.mAlphaGradual = alphaGradual;
resetItems(getFirstVisiblePosition(), mCurrentPositon, mWheelSize/2);
}
/**
* 設(shè)置滾輪可顯示的刻度數(shù)量,必須為奇數(shù),且大于等于3
*
* @param wheelSize
* @throws CycleWheelViewException 滾輪數(shù)量錯(cuò)誤
*/
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();
}
}
/**
* 設(shè)置塊的顏色
* @param unselectedSolidColor 未選中的塊的顏色
* @param selectedSolidColor 選中的塊的顏色
*/
public void setSolid(int unselectedSolidColor, int selectedSolidColor){
this.solidColor = unselectedSolidColor;
this.seletedSolidColor = selectedSolidColor;
initView();
}
/**
* 設(shè)置分割線樣式
* @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>
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
分析Android常見(jiàn)的內(nèi)存泄露和解決方案
內(nèi)存泄漏(Memory Leak)是指程序中己動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無(wú)法釋放,造成系統(tǒng)內(nèi)存的浪費(fèi),導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)崩潰 (OOM) 等嚴(yán)重后果2021-06-06
詳細(xì)講解AsyncTask使用說(shuō)明(值得收藏)
AsyncTask就相當(dāng)于Android給我們提供了一個(gè)多線程編程的一個(gè)框架,其介于Thread和Handler之間,我們?nèi)绻x一個(gè)AsyncTask,就需要定義一個(gè)類來(lái)繼承AsyncTask這個(gè)抽象類,并實(shí)現(xiàn)其唯一的一doInBackgroud 抽象方法,這篇文章主要介紹了AsyncTask詳解,需要的朋友可以參考下2024-01-01
Android.mk文件中添加第三方j(luò)ar文件的方法
這篇文章主要介紹了Android.mk文件中添加第三方j(luò)ar文件及引用第三方j(luò)ar包的方法,需要的朋友可以參考下2018-01-01
Android自定義view實(shí)現(xiàn)仿抖音點(diǎn)贊效果
這篇文章主要介紹了Android自定義view實(shí)現(xiàn)仿抖音點(diǎn)贊效果,代碼簡(jiǎn)單易懂非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-05-05
基于App自適應(yīng)draw9patch不失真背景的方法詳解
本篇文章是對(duì)App自適應(yīng)draw9patch不失真背景的方法進(jìn)行了詳細(xì)的分析介紹。需要的朋友參考下2013-05-05
Android實(shí)現(xiàn)QQ登錄界面遇到問(wèn)題及解決方法
本文給大家介紹android仿qq登錄界面的實(shí)現(xiàn)代碼,在實(shí)現(xiàn)此功能過(guò)程中遇到各種問(wèn)題,但是最終都順利解決,如果大家對(duì)android qq登錄界面實(shí)現(xiàn)方法感興趣的朋友一起學(xué)習(xí)吧2016-09-09
Android Flutter自適應(yīng)瀑布流案例詳解
這篇文章主要介紹了Android Flutter自適應(yīng)瀑布流案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09

