3DWheelPicker

Introduction: Android 数据选择器,支持 3D 滚轮滚动效果,支持各种时间选择,城市选择,普通数据选择,级联数据选择
More: Author   ReportBugs   
Tags:

3D 效果 数据选择控件,源码地址:https://github.com/yijiebuyi/3DWheelPicker 类似效果的开源库也有几个,公司项目也用到类似于时间选择功能,但还达不到产品所要求的,效果并没有那么丝滑。所以自己才实现,下载 Demo 即可体验。

Demo 下载

APK 下载链接 1:http://d.firim.pro/3dwheelpicker

效果图

3d 数据选择效果

功能

  • 时间选择(生日模式,时间段模式,未来时间模式)
  • 单数组的数据选择
  • 多数组的数据选择(支持多级联)
  • 省市区级联城市选择(城市数据可能不完整)
  • 可动态设置样式

使用

  • 在 project 的 build.gradle 添加如下代码

    allprojects {
     repositories {
         ...
         maven { url "https://jitpack.io" }
     }
    }
    
  • 引用 ```gradle

dependencies { implementation 'com.github.yijiebuyi:3DWheelPicker:v1.2.0' }


### 基本用法:
#### 使用 DataPicker (可参照 demo 的用法)


#### 获取单行数据具体使用
- 使用示例
```java
PickOption option = new PickOption.Builder()
                .setVisibleItemCount(9) //设置 pickerView 有多少个可见的 item,必须是单数(1,3,5,7....)
                .setItemSpace(context.getResources().getDimensionPixelOffset(R.dimen.px20)) //设置 item 的间距
                .setItemTextColor(context.getResources().getColor(R.color.font_black)) //设置 item 的文本颜色
                .setItemTextSize(context.getResources().getDimensionPixelSize(R.dimen.font_36px)) //设置 item 的字体大小
                .setVerPadding(context.getResources().getDimensionPixelSize(R.dimen.px20)) //设置 item 的顶部,底部的 padding
                .setShadowGravity(AbstractViewWheelPicker.SHADOW_RIGHT) //设置滚动的偏向
                .setShadowFactor(0.5f) //设置滚轮的偏向因子
                .setFingerMoveFactor(0.8f) //设置手指滑动的阻尼因子
                .setFlingAnimFactor(0.7f) //设置手指快速放开后,滚动动画的阻尼因子
                .setOverScrollOffset(context.getResources().getDimensionPixelSize(R.dimen.px36)) //设置滚轮滑动到底端顶端回滚动画的最大偏移
                .setBackgroundColor(Color.WHITE) //设置滚轮的背景颜色
                .setLeftTitleColor(0xFF1233DD) //设置底部弹出框左边文本的颜色
                .setRightTitleColor(0xFF1233DD) //设置底部弹出框右边文本的颜色
                .setMiddleTitleColor(0xFF333333) //设置底部弹出框中间文本的颜色
                .setTitleBackground(0XFFDDDDDD) //设置底部弹框 title 栏的背景颜色
                .setLeftTitleText("取消") //设置底部弹出框左边文本
                .setRightTitleText("确定") //设置底部弹出框右边文本
                .setMiddleTitleText("请选择数据") //设置底部弹出框中间
                .setTitleHeight(context.getResources().getDimensionPixelOffset(R.dimen.px80)) //设置底部弹框 title 高度
                .build();
 DataPicker.pickData(MainActivity.this, mInitData, getStudents(1), option, new OnDataPickListener<Student>() {
     @Override
     public void onDataPicked(int index, String val, Student data) {
         mInitData = data;
         Toast.makeText(MainActivity.this, val, Toast.LENGTH_SHORT).show();
     }
 });
  • 也可以使用默认的 Builder,然后设置自己关注的属性

      PickOption option = PickOption.getPickDefaultOptionBuilder(mContext)
      .setShadowFactor(0.5f) //设置滚轮的偏向因子
      .setFingerMoveFactor(0.8f) //设置手指滑动的阻尼因子
      .setFlingAnimFactor(0.7f) //设置手指快速放开后,滚动动画的阻尼因子
      .build();
    
  • 时间选择

        /**
       * 获取日期
       *
       * @param context
       * @param initDate 初始化时选择的日期
       * @param mode     获取哪一种数据
       * @param option
       * @param listener
       */
      public static void pickDate(Context context, @Nullable Date initDate, int mode,
                                  @Nullable PickOption option,
                                  final OnDatePickListener listener) 
    
      /**
       * 获取日期 (某个时间段范围)
       *
       * @param context
       * @param initDate
       * @param mode
       * @param from     开始日期
       * @param to       结束日期
       * @param option
       * @param listener
       */
      public static void pickDate(Context context, @Nullable Date initDate, int mode,
                                  long from, long to,
                                  @Nullable PickOption option,
                                  final OnDatePickListener listener) {
    
    • 数据选择: 为了保证 picker 控件显示的数据是期望的字符串,需要对数组中的类(String 数组除外)实现 PickString,或者重写 toString ```java /**

      • 功能描述:当没有实现 PickString,picker 控件上显示是 toString()的内容 */ class Student implements PickString { public String name; public int age;

        public Student(String n, int a) {

        name = n;
        age = a;
        

        }

        @NonNull @Override public String toString() {

        return age + "岁";
        

        }

        @Override public String pickDisplayName() {

        return name;
        

        } }

      /**

      • 获取单行数据
      • @param context
      • @param initData 初始化时,显示的数据
      • @param srcData 数据集合
      • @param listener dataPicker 数据被选中监听器
      • @param */ public static void pickData(Context context, @Nullable T initData, @NonNull final List srcData,
                                 @Nullable PickOption option, final OnDataPickListener listener
        

      /**

      • 多行数据选择(级联数据)
      • @param context
      • @param initIndex 每个 Wheelpicker 是初始化时,对应显示的数据 index,如果没有特殊需求,传 null
      • @param srcData 源数据,是一个二位数组,外层 List 代表是 WheelPicker 的集合,内层 List 代表具体的 Wheelpicker 对应的数据源
      • @param listener dataPicker 数据被选中监听器
      • @param */ public static void pickData(Context context, @Nullable List initIndex, @NonNull List> srcData,
                                 @Nullable PickOption option, final OnMultiDataPickListener listener)
        

      /**

      • 多行数据选择(级联数据)
      • @param context
      • @param initIndex 每个 Wheelpicker 是初始化时,对应显示的数据 index,如果没有特殊需求,传 null
      • @param srcData 源数据,是一个二位数组,外层 List 代表是 WheelPicker 的集合,内层 List 代表具体的 Wheelpicker 对应的数据源
      • @param listener dataPicker 数据被选中监听器
      • @param cascadeListener 级联监听器,这里需要自己去实现级联的数据源,可参考 demo 中城市数据的使用方式
      • @param */ public static void pickData(Context context, @Nullable List initIndex, @NonNull List> srcData,
                                 @Nullable PickOption option, boolean wrapper,
                                 final OnMultiDataPickListener listener, final OnCascadeWheelListener cascadeListener)
        

#### 设置滚轮样式(见 DataPicker 中的使用方法)
pickerview 的样式详情见 PickOption 里面的属性,包括弹出框的顶部 title 样式,pickerview 的的 wheel 样式,item 样式
```java     
    /**
     * 设置滚轮样式
     * @param pickerView
     * @param option
     */
    private static void setPickViewStyle(IPickerView pickerView, PickOption option) {
        //设置 view 样式
        pickerView.asView().setBackgroundColor(option.getBackgroundColor());
        pickerView.asView().setPadding(0, option.getVerPadding(), 0, option.getVerPadding());

        //设置 Item 样式
        pickerView.setTextColor(option.getItemTextColor()); //设置 item 的文本颜色
        pickerView.setVisibleItemCount(option.getVisibleItemCount()); //设置可见 item 的数量,必须是奇数: 如 1,3,5,7,9...
        pickerView.setTextSize(option.getItemTextSize());//设置 item 的文本字体大小
        pickerView.setItemSpace(option.getItemSpace());//设置 item 的间距
        pickerView.setLineColor(option.getItemLineColor());//设置 item 的分割线的颜色
        pickerView.setLineWidth(option.getItemLineWidth());//设置 item 分割线的宽度

        //设置滚轮效果
        pickerView.setShadow(option.getShadowGravity(), option.getShadowFactor()); //设置滚轮偏向,偏向因子(偏向因子取值[0,1])
        pickerView.setScrollMoveFactor(option.getFingerMoveFactor()); //设置手指移动是 item 跟随滚动灵敏度(取值(0,1])
        pickerView.setScrollAnimFactor(option.getFlingAnimFactor()); //设置滚动动画阻尼因子(取值(0,1])
        pickerView.setScrollOverOffset(option.getOverScrollOffset()); //设置滚轮滑动到顶端或底端的最大回弹的偏移量
    }
/**
     * 获取底部弹出框
     * @param context
     * @param pickerView
     * @return
     */
    private static BottomSheet buildBottomSheet(Context context, @Nullable PickOption option, IPickerView pickerView) {
        BottomSheet bottomSheet = new BottomSheet(context);
        if (option != null) {
            bottomSheet.setLeftBtnText(option.getLeftTitleText());
            bottomSheet.setRightBtnText(option.getRightTitleText());
            bottomSheet.setMiddleText(option.getMiddleTitleText());
            bottomSheet.setLeftBtnTextColor(option.getLeftTitleColor());
            bottomSheet.setRightBtnTextColor(option.getRightTitleColor());
            bottomSheet.setMiddleTextColor(option.getMiddleTitleColor());
            bottomSheet.setTitleBackground(option.getTitleBackground());

            bottomSheet.setTitleHeight(option.getTitleHeight());
        }
        bottomSheet.setContent(pickerView.asView());
        return bottomSheet;
    }

级联数据使用案例 (城市选择)


        //城市选择(级联操作)设置 OnCascadeWheelListener 即可满足级联
        findViewById(R.id.city_picker).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                pickCity(AdministrativeUtil.PROVINCE_CITY_AREA, mCascadeInitIndex);
            }
        });

    private void pickCity(int mode, final List<Integer> initIndex) {
        if (mAdministrativeMap == null) {
            mAdministrativeMap = AdministrativeUtil.loadCity(MainActivity.this);
        }

        PickOption option = getPickDefaultOptionBuilder(mContext)
                .setMiddleTitleText("请选择城市")
                .setFlingAnimFactor(0.4f)
                .setVisibleItemCount(7)
                .setItemTextSize(mContext.getResources().getDimensionPixelSize(com.wheelpicker.R.dimen.font_24px))
                .setItemLineColor(0xFF558800)
                .build();

        DataPicker.pickData(mContext, initIndex,
                AdministrativeUtil.getPickData(mAdministrativeMap, initIndex, mode), option,
                new OnMultiDataPickListener() {
                    @Override
                    public void onDataPicked(List indexArr, List val, List data) {
                        String s = indexArr.toString() + ":" + val.toString();
                        Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
                        initIndex.clear();
                        initIndex.addAll(indexArr);
                    }
                }, new OnCascadeWheelListener<List<?>>() {

                    @Override
                    public List<?> onCascade(int wheelIndex, List<Integer> itemIndex) {
                        //级联数据
                        if (wheelIndex == 0) {
                            return mAdministrativeMap.provinces.get(itemIndex.get(0)).city;
                        } else if (wheelIndex == 1) {
                            return mAdministrativeMap.provinces.get(itemIndex.get(0)).city.get(itemIndex.get(1)).areas;
                        }

                        return null;
                    }
                });
    }

也可以使用 Textwheelpicker

   参照 DateWheelPicker,SingleTextWheelPicker

实现方式

继承 View,重写 onDraw(Canvas canvas) 方法实现绘制逻辑

核心类:TextWheelPicker,使用它就可以实现你滚轮的效果,其 drawItems(Canvas canvas)就是绘制滚轮效果的核心逻辑,使用系统的 Camera 类,Matrix 矩阵变化,实现 3d 滚轮效果。

绘制核心代码

    .... 

    float space = computeSpace(rotateDegree, mRadius);
    float relDegree = Math.abs(rotateDegree) / 90;
    canvas.save();
    mCamera.save();
    mRotateMatrix.reset();

    //旋转矩阵变换
    if (mShadowGravity == SHADOW_RIGHT) {
        mCamera.translate(-mShadowOffset, 0, 0);
    } else if (mShadowGravity == SHADOW_LEFT) {
        mCamera.translate(mShadowOffset, 0, 0);
    }
    //旋转
    mWheelPickerImpl.rotateCamera(mCamera, rotateDegree);
    mCamera.getMatrix(mRotateMatrix);
    mCamera.restore();
    mWheelPickerImpl.matrixToCenter(mRotateMatrix, space, mWheelCenterX, mWheelCenterY);
    if (mShadowGravity == SHADOW_RIGHT) {
        mRotateMatrix.postTranslate(mShadowOffset, 0);
    } else if (mShadowGravity == SHADOW_LEFT) {
        mRotateMatrix.postTranslate(-mShadowOffset, 0);
    }
    //偏移矩阵变换
    float depth = computeDepth(rotateDegree, mRelRadius);
    mCamera.save();
    mDepthMatrix.reset();
    mCamera.translate(0, 0, depth);
    mCamera.getMatrix(mDepthMatrix);
    mCamera.restore();
    mWheelPickerImpl.matrixToCenter(mDepthMatrix, space, mWheelCenterX, mWheelCenterY);

    mRotateMatrix.postConcat(mDepthMatrix);
    canvas.concat(mRotateMatrix);

    //绘制文本
    .......

相关关联类:ScrollWheelPicker ;AbstractWheelPicker

原理

滚轮计算原理图(图片来源网络,见文末参考引用连接)

参考文献

滚轮计算原理图:https://blog.csdn.net/qq_22393017/article/details/59488906

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools