宝塔服务器面板,一键全能部署及管理,送你10850元礼包,点我领取

Android 仿拼多多可水平滚动RecyclerView,自定义滚动条滚动距离

2020年,希望大家一切平安如意,毕竟这是个出人意料的多事之秋。

一.效果图:

Android 仿拼多多可水平滚动RecyclerView,自定义滚动条滚动距离-风君子博客

Android 仿拼多多可水平滚动RecyclerView,自定义滚动条滚动距离-风君子博客

 

二.快速实现:

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;@Overrideprotected 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是需要绑定滚动条的RecyclerViewmRvAgeList.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);}@Overridepublic 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() {@Overridepublic void onWheelItemChanged(WheelView wheelView, int position) {List<String> items = wheelView.getItems();String num = items.get(position);tvAge.setText(num+"");//根据改变的位置设置年龄}@Overridepublic void onWheelItemSelected(WheelView wheelView, int position) {age_num = position + 18;//选中数字因为position是从0开始的,所以要加上你初始化起始的数字大小
//                List<String> 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() {@Overridepublic 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()+"");}}@Overridepublic 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.布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#fff"xmlns:app="http://schemas.android.com/apk/res-auto"android:orientation="vertical"><TextViewandroid:id="@+id/tv_age"android:text="--"android:layout_marginTop="20dp"android:layout_gravity="center"android:layout_width="wrap_content"android:layout_height="wrap_content"/><me.samlss.utils.WheelViewandroid:id="@+id/rsv_ruler"android:layout_width="match_parent"android:layout_height="66dp"android:layout_marginLeft="20dp"android:layout_marginTop="50dp"android:layout_marginRight="20dp"android:scrollbarFadeDuration="1"app:lwvCenterMarkTextSize="18sp"app:lwvCursorSize="10dp"app:lwvMarkTextSize="15sp" /><android.support.v7.widget.RecyclerViewandroid:id="@+id/RvScroll"android:layout_gravity="center"android:layout_marginTop="20dp"android:layout_width="match_parent"android:layout_height="66dp"/><!--    <me.samlss.utils.MyHorizontalScrollView-->
<!--        android:layout_marginTop="20dp"-->
<!--        android:layout_width="match_parent"-->
<!--        android:layout_height="66dp">--><!--    </me.samlss.utils.MyHorizontalScrollView>--><android.support.v7.widget.RecyclerViewandroid:id="@+id/RvAgeList"android:layout_gravity="center"android:layout_width="match_parent"android:layout_height="66dp"/><FrameLayoutandroid:id="@+id/lay_slip"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:visibility="visible"><Viewandroid:layout_width="60dp"android:layout_height="8dp"android:background="@drawable/shape_bg_slip_behind" /><Viewandroid:id="@+id/view_slip_front"android:layout_width="40dp"android:layout_height="7.5dp"android:layout_gravity="center_vertical"android:layout_marginLeft="1dp"android:layout_marginRight="1dp"android:background="@drawable/shape_bg_slip_front" /></FrameLayout><FrameLayoutandroid:layout_marginTop="10dp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:visibility="visible"><Viewandroid:layout_width="100dp"android:layout_height="6dp"android:background="@drawable/shape_bg_slip_behind2" /><Viewandroid:id="@+id/view_slip_front02"android:layout_width="60dp"android:layout_height="6dp"android:layout_gravity="center_vertical"android:background="@drawable/shape_bg_slip_front2" /></FrameLayout><FrameLayoutandroid:layout_marginTop="10dp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:visibility="visible"><Viewandroid:layout_width="100dp"android:layout_height="6dp"android:background="@drawable/shape_bg_slip_behind2" /><Viewandroid:id="@+id/view_slip_front03"android:layout_width="60dp"android:layout_height="6dp"android:layout_gravity="center_vertical"android:background="@drawable/shape_bg_slip_front3" /></FrameLayout><FrameLayoutandroid:layout_marginTop="10dp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:visibility="visible"><Viewandroid:layout_width="150dp"android:layout_height="10dp"android:background="@drawable/shape_bg_slip_behind2" /><Viewandroid:id="@+id/view_slip_front04"android:layout_width="80dp"android:layout_height="10dp"android:layout_gravity="center_vertical"android:background="@drawable/shape_bg_slip_front4" /></FrameLayout></LinearLayout>

3.shape绘制圆角背景、渐变色背景

 shape_bg_slip_behind.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><solid android:color="@color/gray_line" /><corners android:radius="5dp" />
</shape>

shape_bg_slip_front.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><solid android:color="@color/colorAccent" /><corners android:radius="5dp" />
</shape>

shape_bg_slip_behind2.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><solid android:color="#ccc" /><corners android:radius="5dp" />
</shape>

 shape_bg_slip_front2.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><solid android:color="#ff6600" /><corners android:radius="5dp" />
</shape>

 shape_bg_slip_front3.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><solid android:color="#ff6600" /><corners android:radius="5dp" /><gradientandroid:angle="0"android:endColor="#FF38C9"android:startColor="#B463FF" />
</shape>

shape_bg_slip_front4.xml: 

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><solid android:color="#ff6600" /><corners android:radius="5dp" /><gradientandroid:angle="90"android:endColor="#FF38C9"android:startColor="#B463FF" />
</shape>

 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<String> 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 ---- startprivate 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 ---- endprivate 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;}@Overrideprotected 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);}@Overrideprotected 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);}}@Overrideprotected 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 endsif (mCenterIndex == mMaxSelectableIndex) {end += mViewScopeSize;} else if (mCenterIndex == mMinSelectableIndex) {start -= mViewScopeSize;}float x = start * mIntervalDis;float markHeight = mHeight - mBottomSpace - mCenterTextSize - mTopSpace;// small scale Y offsetfloat 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 markfor (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 markmMarkPaint.setStrokeWidth(mCenterMarkWidth);canvas.drawLine(ox, mTopSpace, ox, mTopSpace + markHeight, mMarkPaint);} else {// other small markmMarkPaint.setStrokeWidth(mMarkWidth);canvas.drawLine(ox, mTopSpace + smallMarkShrinkY, ox, mTopSpace + markHeight - smallMarkShrinkY, mMarkPaint);}}// mark textif (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;}}@Overridepublic 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);}@Overridepublic 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() {@Overridepublic 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<String> getItems() {return mItems;}public void setItems(List<String> 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;}@Overridepublic boolean onDown(MotionEvent e) {if (!mScroller.isFinished()) {mScroller.forceFinished(false);}mFling = false;if (null != getParent()) {getParent().requestDisallowInterceptTouchEvent(true);}return true;}@Overridepublic void onShowPress(MotionEvent e) {}@Overridepublic boolean onSingleTapUp(MotionEvent e) {playSoundEffect(SoundEffectConstants.CLICK);refreshCenter((int) (getScrollX() + e.getX() - mMaxOverScrollDistance));autoSettle();return true;}@Overridepublic void onLongPress(MotionEvent e) {}@Overridepublic 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;}@Overridepublic 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;}}@Overridepublic Parcelable onSaveInstanceState() {Parcelable superState = super.onSaveInstanceState();SavedState ss = new SavedState(superState);ss.index = getSelectedPosition();ss.min = mMinSelectableIndex;ss.max = mMaxSelectableIndex;return ss;}@Overridepublic 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<SavedState> CREATOR= new Creator<SavedState>() {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();}@Overridepublic void writeToParcel(Parcel out, int flags) {super.writeToParcel(out, flags);out.writeInt(index);out.writeInt(min);out.writeInt(max);}@Overridepublic String toString() {return "WheelView.SavedState{"+ Integer.toHexString(System.identityHashCode(this))+ " index=" + index + " min=" + min + " max=" + max + "}";}}
}

6.attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="lwvWheelView"><attr name="lwvHighlightColor" format="color|reference"/><attr name="lwvMarkColor" format="color|reference"/><attr name="lwvMarkTextColor" format="color|reference"/><attr name="lwvIntervalFactor" format="float"/><attr name="lwvMarkRatio" format="float"/><attr name="lwvCursorSize" format="dimension"/><attr name="lwvMarkTextSize" format="dimension"/><attr name="lwvCenterMarkTextSize" format="dimension"/><attr name="lwvAdditionalCenterMark" format="string|reference"/></declare-styleable>
</resources>

7.颜色:

<?xml version="1.0" encoding="utf-8"?>
<resources><color name="colorPrimary">#333333</color><color name="colorPrimaryDark">#666666</color><color name="colorAccent">#D81B60</color><color name="black">#333</color><color name="gray_line">#888</color>
</resources>