Android 仿拼多多可水平滚动RecyclerView,自定义滚动条滚动距离
2020年,希望大家一切平安如意,毕竟这是个出人意料的多事之秋。
一.效果图:
二.快速实现:
1.主函数代码:
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import me.samlss.broccoli.Broccoli;
import me.samlss.broccoli.PlaceholderParameter;
import me.samlss.utils.ScreenUtils;
import me.samlss.utils.WheelView;
/**
* 可参考
* https://blog.csdn.net/shenggaofei/article/details/78186177#comments_12759724
* https://blog.csdn.net/Blog_Sun/article/details/95338124
*
* https://blog.csdn.net/hhw332704304/article/details/88971381
* https://blog.csdn.net/u010731746/article/details/83303190
* https://blog.csdn.net/qq_36347817/article/details/103529540?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
*/
public class WheelActivity extends AppCompatActivity {
private Broccoli mBroccoli;
private Handler mHandler = new Handler();
private WheelView mWheelView;
private TextView tvAge;
private PersonAgeAdapter mAgeAdapter;
private MyAdapter mAdapter;
private RecyclerView mRvAgeList,RvScroll;
private int age_num= 0;
private int mLastValue= 0;
private int START_NUM= 12;
private int END_NUM= 99;
// private int [] endX;
float endX = 0;
private View view_slip_front,view_slip_front2,view_slip_front3,view_slip_front4;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wheel);
view_slip_front = findViewById(R.id.view_slip_front);
view_slip_front2 = findViewById(R.id.view_slip_front02);
view_slip_front3 = findViewById(R.id.view_slip_front03);
view_slip_front4 = findViewById(R.id.view_slip_front04);
mRvAgeList = findViewById(R.id.RvAgeList);
RvScroll = findViewById(R.id.RvScroll);
mWheelView = findViewById(R.id.rsv_ruler);
tvAge = findViewById(R.id.tv_age);
confitAgeWheelView();
initAgeList();
// initDatas();
initView();
}
/**
* 仿拼多多可水平滚动RecyclerView,自定义滚动条滚动距离
*/
private void initView() {
LinearLayoutManager manager=new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false);
mRvAgeList.setLayoutManager(manager);
mAdapter=new MyAdapter(this,START_NUM,22);
mRvAgeList.setAdapter(mAdapter);
// 这里的mRvHx是需要绑定滚动条的RecyclerView
mRvAgeList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// float endX;
// 整体的总宽度,注意是整体,包括在显示区域之外的。
// int range = mRvAgeList.computeHorizontalScrollRange();
// float density = getScreenDensity();
// // 计算出溢出部分的宽度,即屏幕外剩下的宽度
// float maxEndX = range - ScreenUtils.getScreenWidth(WheelActivity.this) + (25 * density) + 5;
// // 滑动的距离
//// endX[0] = endX[0] + dx;
//// // 计算比例
//// float proportion = endX[0] / maxEndX;
//
// //滑动的距离
// endX += dx;
// //计算比例
// float proportion = endX / maxEndX;
//
// // 计算滚动条宽度
// int transMaxRange = ((ViewGroup) view_slip_front.getParent()).getWidth() - view_slip_front.getWidth();
// // 设置滚动条移动
// view_slip_front.setTranslationX(transMaxRange * proportion);
//整体的总宽度,注意是整体,包括在显示区域之外的。
int range = mRvAgeList.computeHorizontalScrollRange();
float density = getScreenDensity();
//计算出溢出部分的宽度,即屏幕外剩下的宽度
float maxEndX = range + (10 * density) + 5 - ScreenUtils.getScreenWidth(WheelActivity.this);
//滑动的距离
endX += dx;
//计算比例
float proportion = endX / maxEndX;
//计算滚动条宽度
int transMaxRange = ((ViewGroup) view_slip_front.getParent()).getWidth() - view_slip_front.getWidth();
//设置滚动条移动
view_slip_front.setTranslationX(transMaxRange * proportion);
//02
//计算滚动条宽度
int transMaxRange2 = ((ViewGroup) view_slip_front2.getParent()).getWidth() - view_slip_front2.getWidth();
//设置滚动条移动
view_slip_front2.setTranslationX(transMaxRange2 * proportion);
//03
//计算滚动条宽度
int transMaxRange3 = ((ViewGroup) view_slip_front3.getParent()).getWidth() - view_slip_front3.getWidth();
//设置滚动条移动
view_slip_front3.setTranslationX(transMaxRange3 * proportion);
//04
//计算滚动条宽度
int transMaxRange4 = ((ViewGroup) view_slip_front4.getParent()).getWidth() - view_slip_front4.getWidth();
//设置滚动条移动
view_slip_front4.setTranslationX(transMaxRange4 * proportion);
}
});
}
public float getScreenDensity() {
WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
if (wm != null) {
wm.getDefaultDisplay().getMetrics(dm);
}
int width = dm.widthPixels;// 屏幕宽度(像素)
int height = dm.heightPixels; // 屏幕高度(像素)
float density = dm.density;//屏幕密度(0.75 / 1.0 / 1.5)
int densityDpi = dm.densityDpi;//屏幕密度dpi(120 / 160 / 240)
return density;
}
/**
* Android WheelView横向选择器
*/
private void confitAgeWheelView() {
ArrayList localArrayList = new ArrayList();
int i = 18;
while (i <= 60) {
StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append(String.valueOf(i));
localStringBuilder.append("岁");
localArrayList.add(localStringBuilder.toString());
i += 1;
}
mWheelView.setItems(localArrayList);
mWheelView.selectIndex(0);//设置默认选择的年龄
tvAge.setText(mWheelView.getItems().get(0));//设置默认显示年龄
// mWheelView.selectIndex(6);
//监听
mWheelView.setOnWheelItemSelectedListener(new WheelView.OnWheelItemSelectedListener() {
@Override
public void onWheelItemChanged(WheelView wheelView, int position) {
List items = wheelView.getItems();
String num = items.get(position);
tvAge.setText(num+"");//根据改变的位置设置年龄
}
@Override
public void onWheelItemSelected(WheelView wheelView, int position) {
age_num = position + 18;//选中数字因为position是从0开始的,所以要加上你初始化起始的数字大小
// List items = wheelView.getItems();
// String num = items.get(position);
// String nums = items.get(position);
// tvAge.setText(num+"");
}
});
}
/**
* 初始化年龄滑动条
*/
private void initAgeList() {
LinearLayoutManager mLayoutManager =
new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
RvScroll.setLayoutManager(mLayoutManager);
mAgeAdapter = new PersonAgeAdapter(START_NUM, END_NUM,WheelActivity.this);
RvScroll.setAdapter(mAgeAdapter);
RvScroll.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
// mBDownStep.setEnabled(false);
// 效果在暂停时显示, 否则会导致重绘异常
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
mAgeAdapter.highlightItem(getMiddlePosition());
RvScroll.scrollToPosition(getScrollPosition());
mLastValue = getMiddlePosition();
// UserInfoManager.setAge(getMiddlePosition() + START_NUM);
// mBDownStep.setEnabled(true); // 滑动时不可用, 停止时才可以
}
}
@Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
// 值是实时增加
tvAge.setText(String.valueOf(getMiddlePosition() + START_NUM));
}
});
mAgeAdapter.highlightItem(getMiddlePosition());
}
/**
* 获取中间位置
*
* @return 当前值
*/
private int getMiddlePosition() {
return getScrollPosition() + (PersonAgeAdapter.ITEM_NUM / 2);
}
/**
* 获取滑动值, 滑动偏移 / 每个格子宽度
*
* @return 当前值
*/
private int getScrollPosition() {
return (int) ((double) RvScroll.computeHorizontalScrollOffset()
/ (double) PersonAgeAdapter.getItemStdWidth());
}
/**
* 初始化数据
*/
private void initDatas() {
LinearLayoutManager manager=new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false);
mRvAgeList.setLayoutManager(manager);
mAdapter=new MyAdapter(this,START_NUM,END_NUM);
mRvAgeList.setAdapter(mAdapter);
mRvAgeList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(newState==RecyclerView.SCROLL_STATE_IDLE){
mAdapter.highlightItem(getMiddlePositions());
//将位置移动到中间位置
((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPositionWithOffset(getScrollPositions(),0);
System.out.println(getScrollPositions()+"");
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
tvAge.setText(String.valueOf(getMiddlePositions() + START_NUM));
}
});
mAdapter.highlightItem(getMiddlePositions());
}
/**
* 获取中间位置的position
* @return
*/
private int getMiddlePositions() {
return getScrollPositions()+(mAdapter.ITEM_NUM/2);
}
/**
* 获取滑动值, 滑动偏移 / 每个格子宽度
*
* @return 当前值
*/
private int getScrollPositions() {
return (int) (((double) mRvAgeList.computeHorizontalScrollOffset()
/ (double) mAdapter.getItemWidth())+0.5f);
}
}
2.布局:
3.shape绘制圆角背景、渐变色背景
shape_bg_slip_behind.xml:
shape_bg_slip_front.xml:
shape_bg_slip_behind2.xml:
shape_bg_slip_front2.xml:
shape_bg_slip_front3.xml:
shape_bg_slip_front4.xml:
4.ScreenUtils获得屏幕相关的辅助类
package me.samlss.utils;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.WindowManager;
//获得屏幕相关的辅助类
public class ScreenUtils
{
private ScreenUtils()
{
/* cannot be instantiated */
throw new UnsupportedOperationException("cannot be instantiated");
}
/**
* 获得屏幕高度
*
* @param context
* @return
*/
public static int getScreenWidth(Context context)
{
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.widthPixels;
}
/**
* 获得屏幕宽度
*
* @param context
* @return
*/
public static int getScreenHeight(Context context)
{
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.heightPixels;
}
/**
* 获得状态栏的高度
*
* @param context
* @return
*/
public static int getStatusHeight(Context context)
{
int statusHeight = -1;
try
{
Class> clazz = Class.forName("com.android.internal.R$dimen");
Object object = clazz.newInstance();
int height = Integer.parseInt(clazz.getField("status_bar_height")
.get(object).toString());
statusHeight = context.getResources().getDimensionPixelSize(height);
} catch (Exception e)
{
e.printStackTrace();
}
return statusHeight;
}
/**
* 获取当前屏幕截图,包含状态栏
*
* @param activity
* @return
*/
public static Bitmap snapShotWithStatusBar(Activity activity)
{
View view = activity.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bmp = view.getDrawingCache();
int width = getScreenWidth(activity);
int height = getScreenHeight(activity);
Bitmap bp = null;
bp = Bitmap.createBitmap(bmp, 0, 0, width, height);
view.destroyDrawingCache();
return bp;
}
/**
* 获取当前屏幕截图,不包含状态栏
*
* @param activity
* @return
*/
public static Bitmap snapShotWithoutStatusBar(Activity activity)
{
View view = activity.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bmp = view.getDrawingCache();
Rect frame = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
int width = getScreenWidth(activity);
int height = getScreenHeight(activity);
Bitmap bp = null;
bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height
- statusBarHeight);
view.destroyDrawingCache();
return bp;
}
}
5.自定义类:(以下是Android RecyclerView 实现横向滚动效果)
Android RecyclerView 实现横向滚动效果
package me.samlss.utils;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.ViewCompat;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.widget.OverScroller;
import java.util.ArrayList;
import java.util.List;
import me.samlss.broccoli_demo.R;
/**
*
*/
public class WheelView extends View implements GestureDetector.OnGestureListener {
public static final float DEFAULT_INTERVAL_FACTOR = 1.2f;
public static final float DEFAULT_MARK_RATIO = 0.7f;
private Paint mMarkPaint;
private TextPaint mMarkTextPaint;
private int mCenterIndex = -1;
private int mHighlightColor, mMarkTextColor;
private int mMarkColor, mFadeMarkColor;
private int mHeight;
private List mItems;
private String mAdditionCenterMark;
private OnWheelItemSelectedListener mOnWheelItemSelectedListener;
private float mIntervalFactor = DEFAULT_INTERVAL_FACTOR;
private float mMarkRatio = DEFAULT_MARK_RATIO;
private int mMarkCount;
private float mAdditionCenterMarkWidth;
private Path mCenterIndicatorPath = new Path();
private float mCursorSize;
private int mViewScopeSize;
// scroll control args ---- start
private OverScroller mScroller;
private float mMaxOverScrollDistance;
private RectF mContentRectF;
private boolean mFling = false;
private float mCenterTextSize, mNormalTextSize;
private float mTopSpace, mBottomSpace;
private float mIntervalDis;
private float mCenterMarkWidth, mMarkWidth;
private GestureDetectorCompat mGestureDetectorCompat;
// scroll control args ---- end
private int mLastSelectedIndex = -1;
private int mMinSelectableIndex = Integer.MIN_VALUE;
private int mMaxSelectableIndex = Integer.MAX_VALUE;
public WheelView(Context context) {
super(context);
init(null);
}
public WheelView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public WheelView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
protected void init(AttributeSet attrs) {
float density = getResources().getDisplayMetrics().density;
mCenterMarkWidth = (int) (density * 1.5f + 0.5f);
mMarkWidth = density;
mHighlightColor = 0xFFF74C39;
mMarkTextColor = 0xFF666666;
mMarkColor = 0xFFEEEEEE;
mCursorSize = density * 18;
mCenterTextSize = density * 22;
mNormalTextSize = density * 18;
mBottomSpace = density * 6;
TypedArray ta = attrs == null ? null : getContext().obtainStyledAttributes(attrs, R.styleable.lwvWheelView);
if (ta != null) {
mHighlightColor = ta.getColor(R.styleable.lwvWheelView_lwvHighlightColor, mHighlightColor);
mMarkTextColor = ta.getColor(R.styleable.lwvWheelView_lwvMarkTextColor, mMarkTextColor);
mMarkColor = ta.getColor(R.styleable.lwvWheelView_lwvMarkColor, mMarkColor);
mIntervalFactor = ta.getFloat(R.styleable.lwvWheelView_lwvIntervalFactor, mIntervalFactor);
mMarkRatio = ta.getFloat(R.styleable.lwvWheelView_lwvMarkRatio, mMarkRatio);
mAdditionCenterMark = ta.getString(R.styleable.lwvWheelView_lwvAdditionalCenterMark);
mCenterTextSize = ta.getDimension(R.styleable.lwvWheelView_lwvCenterMarkTextSize, mCenterTextSize);
mNormalTextSize = ta.getDimension(R.styleable.lwvWheelView_lwvMarkTextSize, mNormalTextSize);
mCursorSize = ta.getDimension(R.styleable.lwvWheelView_lwvCursorSize, mCursorSize);
}
mFadeMarkColor = mHighlightColor & 0xAAFFFFFF;
mIntervalFactor = Math.max(1, mIntervalFactor);
mMarkRatio = Math.min(1, mMarkRatio);
mTopSpace = mCursorSize + density * 2;
mMarkPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mMarkTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
mMarkTextPaint.setTextAlign(Paint.Align.CENTER);
mMarkTextPaint.setColor(mHighlightColor);
mMarkPaint.setColor(mMarkColor);
mMarkPaint.setStrokeWidth(mCenterMarkWidth);
mMarkTextPaint.setTextSize(mCenterTextSize);
calcIntervalDis();
mScroller = new OverScroller(getContext());
mContentRectF = new RectF();
mGestureDetectorCompat = new GestureDetectorCompat(getContext(), this);
selectIndex(0);
}
/**
* calculate interval distance between items
*/
private void calcIntervalDis() {
if (mMarkTextPaint == null) {
return;
}
String defaultText = "888888";
Rect temp = new Rect();
int max = 0;
if (mItems != null && mItems.size() > 0) {
for (String i : mItems) {
mMarkTextPaint.getTextBounds(i, 0, i.length(), temp);
if (temp.width() > max) {
max = temp.width();
}
}
} else {
mMarkTextPaint.getTextBounds(defaultText, 0, defaultText.length(), temp);
max = temp.width();
}
if (!TextUtils.isEmpty(mAdditionCenterMark)) {
mMarkTextPaint.setTextSize(mNormalTextSize);
mMarkTextPaint.getTextBounds(mAdditionCenterMark, 0, mAdditionCenterMark.length(), temp);
mAdditionCenterMarkWidth = temp.width();
max += temp.width();
}
mIntervalDis = max * mIntervalFactor;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureWidth(int widthMeasureSpec) {
int measureMode = MeasureSpec.getMode(widthMeasureSpec);
int measureSize = MeasureSpec.getSize(widthMeasureSpec);
int result = getSuggestedMinimumWidth();
switch (measureMode) {
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = measureSize;
break;
default:
break;
}
return result;
}
private int measureHeight(int heightMeasure) {
int measureMode = MeasureSpec.getMode(heightMeasure);
int measureSize = MeasureSpec.getSize(heightMeasure);
int result = (int) (mBottomSpace + mTopSpace * 2 + mCenterTextSize);
switch (measureMode) {
case MeasureSpec.EXACTLY:
result = Math.max(result, measureSize);
break;
case MeasureSpec.AT_MOST:
result = Math.min(result, measureSize);
break;
default:
break;
}
return result;
}
public void fling(int velocityX, int velocityY) {
mScroller.fling(getScrollX(), getScrollY(),
velocityX, velocityY,
(int) (-mMaxOverScrollDistance + mMinSelectableIndex * mIntervalDis), (int) (mContentRectF.width() - mMaxOverScrollDistance - (mMarkCount - 1 - mMaxSelectableIndex) * mIntervalDis),
0, 0,
(int) mMaxOverScrollDistance, 0);
ViewCompat.postInvalidateOnAnimation(this);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w != oldw || h != oldh) {
mHeight = h;
mMaxOverScrollDistance = w / 2.f;
mContentRectF.set(0, 0, (mMarkCount - 1) * mIntervalDis, h);
mViewScopeSize = (int) Math.ceil(mMaxOverScrollDistance / mIntervalDis);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mCenterIndicatorPath.reset();
float sizeDiv2 = mCursorSize / 2f;
float sizeDiv3 = mCursorSize / 3f;
mCenterIndicatorPath.moveTo(mMaxOverScrollDistance - sizeDiv2 + getScrollX(), 0);
mCenterIndicatorPath.rLineTo(0, sizeDiv3);
mCenterIndicatorPath.rLineTo(sizeDiv2, sizeDiv2);
mCenterIndicatorPath.rLineTo(sizeDiv2, -sizeDiv2);
mCenterIndicatorPath.rLineTo(0, -sizeDiv3);
mCenterIndicatorPath.close();
mMarkPaint.setColor(mHighlightColor);
canvas.drawPath(mCenterIndicatorPath, mMarkPaint);
int start = mCenterIndex - mViewScopeSize;
int end = mCenterIndex + mViewScopeSize + 1;
start = Math.max(start, -mViewScopeSize * 2);
end = Math.min(end, mMarkCount + mViewScopeSize * 2);
// extends both ends
if (mCenterIndex == mMaxSelectableIndex) {
end += mViewScopeSize;
} else if (mCenterIndex == mMinSelectableIndex) {
start -= mViewScopeSize;
}
float x = start * mIntervalDis;
float markHeight = mHeight - mBottomSpace - mCenterTextSize - mTopSpace;
// small scale Y offset
float smallMarkShrinkY = markHeight * (1 - mMarkRatio) / 2f;
smallMarkShrinkY = Math.min((markHeight - mMarkWidth) / 2f, smallMarkShrinkY);
for (int i = start; i < end; i++) {
float tempDis = mIntervalDis / 5f;
// offset: Small mark offset Big mark
for (int offset = -2; offset < 3; offset++) {
float ox = x + offset * tempDis;
if (i >= 0 && i <= mMarkCount && mCenterIndex == i) {
int tempOffset = Math.abs(offset);
if (tempOffset == 0) {
mMarkPaint.setColor(mHighlightColor);
} else if (tempOffset == 1) {
mMarkPaint.setColor(mFadeMarkColor);
} else {
mMarkPaint.setColor(mMarkColor);
}
} else {
mMarkPaint.setColor(mMarkColor);
}
if (offset == 0) {
// center mark
mMarkPaint.setStrokeWidth(mCenterMarkWidth);
canvas.drawLine(ox, mTopSpace, ox, mTopSpace + markHeight, mMarkPaint);
} else {
// other small mark
mMarkPaint.setStrokeWidth(mMarkWidth);
canvas.drawLine(ox, mTopSpace + smallMarkShrinkY, ox, mTopSpace + markHeight - smallMarkShrinkY, mMarkPaint);
}
}
// mark text
if (mMarkCount > 0 && i >= 0 && i < mMarkCount) {
CharSequence temp = mItems.get(i);
if (mCenterIndex == i) {
mMarkTextPaint.setColor(mHighlightColor);
mMarkTextPaint.setTextSize(mCenterTextSize);
if (!TextUtils.isEmpty(mAdditionCenterMark)) {
float off = mAdditionCenterMarkWidth / 2f;
float tsize = mMarkTextPaint.measureText(temp, 0, temp.length());
canvas.drawText(temp, 0, temp.length(), x - off, mHeight - mBottomSpace, mMarkTextPaint);
mMarkTextPaint.setTextSize(mNormalTextSize);
canvas.drawText(mAdditionCenterMark, x + tsize / 2f, mHeight - mBottomSpace, mMarkTextPaint);
} else {
canvas.drawText(temp, 0, temp.length(), x, mHeight - mBottomSpace, mMarkTextPaint);
}
} else {
mMarkTextPaint.setColor(mMarkTextColor);
mMarkTextPaint.setTextSize(mNormalTextSize);
canvas.drawText(temp, 0, temp.length(), x, mHeight - mBottomSpace, mMarkTextPaint);
}
}
x += mIntervalDis;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mItems == null || mItems.size() == 0 || !isEnabled()) {
return false;
}
boolean ret = mGestureDetectorCompat.onTouchEvent(event);
if (!mFling && MotionEvent.ACTION_UP == event.getAction()) {
autoSettle();
ret = true;
}
return ret || super.onTouchEvent(event);
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
refreshCenter();
invalidate();
} else {
if (mFling) {
mFling = false;
autoSettle();
}
}
}
public void setAdditionCenterMark(String additionCenterMark) {
mAdditionCenterMark = additionCenterMark;
calcIntervalDis();
invalidate();
}
private void autoSettle() {
int sx = getScrollX();
float dx = mCenterIndex * mIntervalDis - sx - mMaxOverScrollDistance;
mScroller.startScroll(sx, 0, (int) dx, 0);
postInvalidate();
if (mLastSelectedIndex != mCenterIndex) {
mLastSelectedIndex = mCenterIndex;
if (null != mOnWheelItemSelectedListener) {
mOnWheelItemSelectedListener.onWheelItemSelected(this, mCenterIndex);
}
}
}
/**
* limit center index in bounds.
*
* @param center
* @return
*/
private int safeCenter(int center) {
if (center < mMinSelectableIndex) {
center = mMinSelectableIndex;
} else if (center > mMaxSelectableIndex) {
center = mMaxSelectableIndex;
}
return center;
}
private void refreshCenter(int offsetX) {
int offset = (int) (offsetX + mMaxOverScrollDistance);
int tempIndex = Math.round(offset / mIntervalDis);
tempIndex = safeCenter(tempIndex);
if (mCenterIndex == tempIndex) {
return;
}
mCenterIndex = tempIndex;
if (null != mOnWheelItemSelectedListener) {
mOnWheelItemSelectedListener.onWheelItemChanged(this, mCenterIndex);
}
}
private void refreshCenter() {
refreshCenter(getScrollX());
}
public void selectIndex(int index) {
mCenterIndex = index;
post(new Runnable() {
@Override
public void run() {
scrollTo((int) (mCenterIndex * mIntervalDis - mMaxOverScrollDistance), 0);
invalidate();
refreshCenter();
}
});
}
public void smoothSelectIndex(int index) {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
int deltaIndex = index - mCenterIndex;
mScroller.startScroll(getScrollX(), 0, (int) (deltaIndex * mIntervalDis), 0);
invalidate();
}
public int getMinSelectableIndex() {
return mMinSelectableIndex;
}
public void setMinSelectableIndex(int minSelectableIndex) {
if (minSelectableIndex > mMaxSelectableIndex) {
minSelectableIndex = mMaxSelectableIndex;
}
mMinSelectableIndex = minSelectableIndex;
int afterCenter = safeCenter(mCenterIndex);
if (afterCenter != mCenterIndex) {
selectIndex(afterCenter);
}
}
public int getMaxSelectableIndex() {
return mMaxSelectableIndex;
}
public void setMaxSelectableIndex(int maxSelectableIndex) {
if (maxSelectableIndex < mMinSelectableIndex) {
maxSelectableIndex = mMinSelectableIndex;
}
mMaxSelectableIndex = maxSelectableIndex;
int afterCenter = safeCenter(mCenterIndex);
if (afterCenter != mCenterIndex) {
selectIndex(afterCenter);
}
}
public List getItems() {
return mItems;
}
public void setItems(List items) {
if (mItems == null) {
mItems = new ArrayList<>();
} else {
mItems.clear();
}
mItems.addAll(items);
mMarkCount = null == mItems ? 0 : mItems.size();
if (mMarkCount > 0) {
mMinSelectableIndex = Math.max(mMinSelectableIndex, 0);
mMaxSelectableIndex = Math.min(mMaxSelectableIndex, mMarkCount - 1);
}
mContentRectF.set(0, 0, (mMarkCount - 1) * mIntervalDis, getMeasuredHeight());
mCenterIndex = Math.min(mCenterIndex, mMarkCount);
calcIntervalDis();
invalidate();
}
public int getSelectedPosition() {
return mCenterIndex;
}
public void setOnWheelItemSelectedListener(OnWheelItemSelectedListener onWheelItemSelectedListener) {
mOnWheelItemSelectedListener = onWheelItemSelectedListener;
}
@Override
public boolean onDown(MotionEvent e) {
if (!mScroller.isFinished()) {
mScroller.forceFinished(false);
}
mFling = false;
if (null != getParent()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
playSoundEffect(SoundEffectConstants.CLICK);
refreshCenter((int) (getScrollX() + e.getX() - mMaxOverScrollDistance));
autoSettle();
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
float dis = distanceX;
float scrollX = getScrollX();
if (scrollX < mMinSelectableIndex * mIntervalDis - 2 * mMaxOverScrollDistance) {
dis = 0;
} else if (scrollX < mMinSelectableIndex * mIntervalDis - mMaxOverScrollDistance) {
dis = distanceX / 4.f;
} else if (scrollX > mContentRectF.width() - (mMarkCount - mMaxSelectableIndex - 1) * mIntervalDis) {
dis = 0;
} else if (scrollX > mContentRectF.width() - (mMarkCount - mMaxSelectableIndex - 1) * mIntervalDis - mMaxOverScrollDistance) {
dis = distanceX / 4.f;
}
scrollBy((int) dis, 0);
refreshCenter();
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
float scrollX = getScrollX();
if (scrollX < -mMaxOverScrollDistance + mMinSelectableIndex * mIntervalDis || scrollX > mContentRectF.width() - mMaxOverScrollDistance - (mMarkCount - 1 - mMaxSelectableIndex) * mIntervalDis) {
return false;
} else {
mFling = true;
fling((int) -velocityX, 0);
return true;
}
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.index = getSelectedPosition();
ss.min = mMinSelectableIndex;
ss.max = mMaxSelectableIndex;
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mMinSelectableIndex = ss.min;
mMaxSelectableIndex = ss.max;
selectIndex(ss.index);
requestLayout();
}
public interface OnWheelItemSelectedListener {
void onWheelItemChanged(WheelView wheelView, int position);
void onWheelItemSelected(WheelView wheelView, int position);
}
static class SavedState extends BaseSavedState {
public static final Creator CREATOR
= new Creator() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
int index;
int min;
int max;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
index = in.readInt();
min = in.readInt();
max = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(index);
out.writeInt(min);
out.writeInt(max);
}
@Override
public String toString() {
return "WheelView.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " index=" + index + " min=" + min + " max=" + max + "}";
}
}
}
6.attrs.xml:
7.颜色:
#333333
#666666
#D81B60
#333
#888