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

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<String> 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<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() {
            @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.布局:

<?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">
    <TextView
        android: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.WheelView
        android: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.RecyclerView
        android: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.RecyclerView
            android:id="@+id/RvAgeList"
            android:layout_gravity="center"
            android:layout_width="match_parent"
            android:layout_height="66dp"/>
   


    <FrameLayout
        android:id="@+id/lay_slip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="visible">

        <View
            android:layout_width="60dp"
            android:layout_height="8dp"
            android:background="@drawable/shape_bg_slip_behind" />

        <View
            android: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>

    <FrameLayout
        android:layout_marginTop="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="visible">

        <View
            android:layout_width="100dp"
            android:layout_height="6dp"
            android:background="@drawable/shape_bg_slip_behind2" />

        <View
            android: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>
    <FrameLayout
        android:layout_marginTop="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="visible">

        <View
            android:layout_width="100dp"
            android:layout_height="6dp"
            android:background="@drawable/shape_bg_slip_behind2" />

        <View
            android: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>
    <FrameLayout
        android:layout_marginTop="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="visible">

        <View
            android:layout_width="150dp"
            android:layout_height="10dp"
            android:background="@drawable/shape_bg_slip_behind2" />

        <View
            android: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" />
    <gradient
        android: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" />
    <gradient
        android: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 ---- 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<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;
    }

    @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<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();
        }

        @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:

<?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>

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要实现 RecyclerView 滚动条高度自定义,你可以考虑使用一个自定义滚动条 View,然后通过监听 RecyclerView滚动事件来控制滚动条的位置和高度。 首先,你需要在 RecyclerView 的布局文件中添加一个自定义滚动条 View,例如: ```xml <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> <com.example.customscrollbar.CustomScrollBar android:id="@+id/custom_scroll_bar" android:layout_width="10dp" android:layout_height="match_parent" android:layout_alignParentRight="true" android:layout_marginRight="10dp" /> </RelativeLayout> ``` 其中,CustomScrollBar 是自定义滚动条 View,它的宽度可以根据需求自行设置。 然后,在 RecyclerView 的代码中,你需要监听 RecyclerView滚动事件,计算滚动条的位置和高度,然后更新 CustomScrollBar 的位置和高度。例如: ```java recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); // 计算当前可见的 item 数量 int visibleItemCount = recyclerView.getLayoutManager().getChildCount(); // 计算所有 item 的数量 int totalItemCount = recyclerView.getLayoutManager().getItemCount(); // 计算第一个可见的 item 的位置 int firstVisibleItemPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition(); // 计算滚动条的高度 int scrollBarHeight = (int) ((float) visibleItemCount / (float) totalItemCount * recyclerView.getHeight()); // 计算滚动条的位置 int scrollBarTop = (int) ((float) firstVisibleItemPosition / (float) totalItemCount * recyclerView.getHeight()); // 更新滚动条的位置和高度 customScrollBar.setScrollBarTop(scrollBarTop); customScrollBar.setScrollBarHeight(scrollBarHeight); } }); ``` 在这段代码中,我们使用了 RecyclerView 的 LayoutManager 中的方法来计算当前可见的 item 数量、所有 item 的数量和第一个可见的 item 的位置,然后根据这些数据计算出滚动条的位置和高度,最后更新 CustomScrollBar 的位置和高度。 最后,你需要在 CustomScrollBar 中实现 setScrollBarTop() 和 setScrollBarHeight() 方法,用于设置滚动条的位置和高度。例如: ```java public void setScrollBarTop(int top) { setY(top); } public void setScrollBarHeight(int height) { getLayoutParams().height = height; requestLayout(); } ``` 这样,你就可以实现 RecyclerView 滚动条高度自定义的效果了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值