Android自定义可循环的滚动选择器CycleWheelView

作者:勤劳的小蜜蜂啊 时间:2023-04-06 00:43:16 

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

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

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

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

补个图片: 

Android自定义可循环的滚动选择器CycleWheelView

不多说了,直接上代码:

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>
标签:Android,滚动,选择器
0
投稿

猜你喜欢

  • android实现圆环倒计时控件

    2021-12-15 20:55:54
  • 解决android 显示内容被底部导航栏遮挡的问题

    2021-08-05 10:10:55
  • Java堆&优先级队列示例讲解(上)

    2023-04-09 11:09:59
  • Java实现邮箱找回密码实例代码

    2022-01-12 11:48:57
  • Java快速排序QuickSort(实例)

    2021-12-22 21:47:42
  • Java二维数组实现数字拼图效果

    2021-11-21 20:39:17
  • 深入理解C♯ 7.0中的Tuple特性

    2023-10-28 09:33:14
  • C# Winform多屏幕多显示器编程技巧实例

    2021-09-19 16:49:09
  • 不使用Math.random方法生成随机数(随机数生成器)

    2021-11-28 05:08:46
  • Android生成带圆角的Bitmap图片

    2022-09-08 11:18:19
  • 浅谈c# 浮点数计算

    2023-08-04 22:10:09
  • Android开机画面的具体修改方法

    2023-05-13 20:52:22
  • SpringBoot设置编码UTF-8的两种方法

    2022-05-04 00:09:08
  • mybatis批量新增、删除、查询和修改方式

    2023-11-23 10:13:01
  • 浅谈java多线程wait,notify

    2023-11-29 16:29:03
  • spring注解之@Valid和@Validated的区分总结

    2023-11-01 07:51:42
  • Android 文件存储系统原理

    2021-12-24 05:10:57
  • 配置tjxCold(idea效率插件)的模版教程详解

    2022-12-20 21:17:57
  • c#生成随机数示例分享

    2023-10-04 06:54:50
  • java中File类的使用方法

    2023-09-11 11:49:55
  • asp之家 软件编程 m.aspxhome.com