Skip to content

yijiebuyi/3DWheelPicker

Repository files navigation

3DWheelPicker

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" }
    }
}
  • 引用
dependencies {
  implementation 'com.github.yijiebuyi:3DWheelPicker:v1.2.0'
}

基本用法:

使用 DataPicker (可参照demo的用法)

获取单行数据具体使用

  • 使用示例
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
   /**
    * 功能描述:当没有实现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 <T>
   */
  public static <T> void pickData(Context context, @Nullable T initData, @NonNull final List<T> srcData, 
                                  @Nullable PickOption option, final OnDataPickListener listener
  
 /**
   * 多行数据选择(级联数据)
   * @param context
   * @param initIndex 每个Wheelpicker是初始化时,对应显示的数据index,如果没有特殊需求,传null
   * @param srcData 源数据,是一个二位数组,外层List代表是WheelPicker的集合,内层List代表具体的Wheelpicker对应的数据源
   * @param listener dataPicker数据被选中监听器
   * @param <T>
   */
  public static <T> void pickData(Context context, @Nullable List<Integer> initIndex, @NonNull List<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 <T>
   */
  public static <T> void pickData(Context context, @Nullable List<Integer> initIndex, @NonNull List<List<?>> srcData,
                                  @Nullable PickOption option, boolean wrapper,
                                  final OnMultiDataPickListener listener, final OnCascadeWheelListener cascadeListener)
  

设置滚轮样式(见DataPicker中的使用方法)

pickerview的样式详情见PickOption里面的属性,包括弹出框的顶部title样式,pickerview的的wheel样式,item样式

    /**
     * 设置滚轮样式
     * @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

   参照 DateWheelPickerSingleTextWheelPicker
   

#实现方式 继承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

About

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

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages