mac
2024-04-26 705006fe0c1187ace53ee6505f71ba98fe4b44e6
2024年04月26日18:26:42
57个文件已添加
2个文件已删除
11个文件已修改
5911 ■■■■■ 已修改文件
app/build.gradle 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/hdl/photovoltaic/HDLApp.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/hdl/photovoltaic/ui/home/HomePageFragment.java 236 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/hdl/photovoltaic/utils/TimeUtils.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/time_selected_38c494.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_home_page.xml 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_station.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values-w1240dp/dimens.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values-w600dp/dimens.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/colors.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/strings.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/themes.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/.gitignore 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/build.gradle 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/proguard-rules.pro 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/androidTest/java/com/bigkoo/pickerview/ApplicationTest.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/AndroidManifest.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/adapter/ArrayWheelAdapter.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/adapter/NumericWheelAdapter.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/builder/OptionsPickerBuilder.java 292 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/builder/TimePickerBuilder.java 315 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/configure/PickerOptions.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/listener/CustomListener.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/listener/ISelectTimeCallback.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/listener/OnDismissListener.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/listener/OnOptionsSelectChangeListener.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/listener/OnOptionsSelectListener.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/listener/OnTimeSelectChangeListener.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/listener/OnTimeSelectListener.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/utils/ChinaDate.java 352 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/utils/LunarCalendar.java 429 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/utils/PickerViewAnimateUtil.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/view/BasePickerView.java 361 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/view/OptionsPickerView.java 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/view/TimePickerView.java 293 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/view/WheelOptions.java 428 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/java/com/bigkoo/pickerview/view/WheelTime.java 900 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/anim/pickerview_dialog_scale_in.xml 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/anim/pickerview_dialog_scale_out.xml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/anim/pickerview_slide_in_bottom.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/anim/pickerview_slide_out_bottom.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/drawable/selector_pickerview_btn.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/layout/include_pickerview_topbar.xml 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/layout/layout_basepickerview.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/layout/pickerview_options.xml 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/layout/pickerview_time.xml 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/values-en/strings.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/values/dimens.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/values/integers.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/values/strings.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pickerview/src/main/res/values/styles.xml 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
settings.gradle 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
third-zxing/build.gradle 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/.gitignore 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/build.gradle 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/proguard-rules.pro 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/androidTest/java/test/wheelview/ExampleInstrumentedTest.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/AndroidManifest.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/java/com/contrarywind/adapter/WheelAdapter.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/java/com/contrarywind/interfaces/IPickerViewData.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/java/com/contrarywind/listener/LoopViewGestureListener.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/java/com/contrarywind/listener/OnItemSelectedListener.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/java/com/contrarywind/timer/InertiaTimerTask.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/java/com/contrarywind/timer/MessageHandler.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/java/com/contrarywind/timer/SmoothScrollTimerTask.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/java/com/contrarywind/view/WheelView.java 840 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/res/values/attrs.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/res/values/colors.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/res/values/dimens.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wheelview/src/main/res/values/strings.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/build.gradle
@@ -121,6 +121,8 @@
    implementation 'androidx.navigation:navigation-fragment:2.3.5'
    implementation 'androidx.navigation:navigation-ui:2.3.5'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    //时间库
    implementation project(path: ':pickerview')
    //图表库
    implementation project(path: ':aa_chart_core')
    //国家列表和手机区号库
@@ -177,10 +179,6 @@
    implementation 'cn.jiguang.sdk:jpush-google:5.2.2'
    //分析和用户标签等功能(可选,集成极光分析SDK后,即可支持行为触发推送消息、推送转化率统计,用户行为分)
    implementation 'cn.jiguang.sdk:joperate:2.0.2'
    //PickerView
    implementation 'com.contrarywind:Android-PickerView:4.1.9'
app/src/main/java/com/hdl/photovoltaic/HDLApp.java
@@ -49,6 +49,7 @@
import cn.jpush.android.api.JPushInterface;
import io.dcloud.common.util.RuningAcitvityUtil;
import me.jessyan.autosize.AutoSize;
import me.jessyan.autosize.AutoSizeConfig;
import me.jessyan.autosize.onAdaptListener;
import me.jessyan.autosize.utils.ScreenUtils;
@@ -73,6 +74,7 @@
    @Override
    public void onCreate() {
        super.onCreate();
        // 获取当前进程的PID
        Log.d("进程===", android.os.Process.myPid() + "");
        mHDLApp = this;
@@ -312,12 +314,14 @@
     * 旋转后高变宽,按旋转后高度适配,所以宽度不写死
     */
    private void initAutoSize() {
        AutoSize.initCompatMultiProcess(this);
        boolean isBaseOnWidth = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
        AutoSizeConfig.getInstance()
                .setBaseOnWidth(isBaseOnWidth)
                .setBaseOnWidth(true)
                .setCustomFragment(true)
                .setDesignWidthInDp(375)
                .setDesignHeightInDp(812)
                .setUseDeviceSize(false)
                .setDesignWidthInDp(isBaseOnWidth ? 375 : 812)
                .setDesignHeightInDp(isBaseOnWidth ? 812 : 375)
                .setOnAdaptListener(new onAdaptListener() {
                    @Override
                    public void onAdaptBefore(Object target, Activity activity) {
@@ -357,4 +361,7 @@
        MultiDex.install(base);
        super.attachBaseContext(base);
    }
}
app/src/main/java/com/hdl/photovoltaic/ui/home/HomePageFragment.java
@@ -53,10 +53,13 @@
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.appcompat.content.res.AppCompatResources;
import com.bigkoo.pickerview.builder.TimePickerBuilder;
import com.bigkoo.pickerview.listener.OnTimeSelectListener;
@@ -67,10 +70,13 @@
import com.github.AAChartModel.AAChartCore.AAChartEnum.AAChartType;
import com.hdl.photovoltaic.R;
import com.hdl.photovoltaic.base.CustomBaseFragment;
import com.hdl.photovoltaic.config.AppConfigManage;
import com.hdl.photovoltaic.config.ConstantManage;
import com.hdl.photovoltaic.config.UserConfigManage;
import com.hdl.photovoltaic.databinding.FragmentHomePageBinding;
import com.hdl.photovoltaic.enums.HomepageTitleTabSwitch;
import com.hdl.photovoltaic.other.HdlLogLogic;
import com.hdl.photovoltaic.utils.TimeUtils;
import com.hdl.sdk.link.core.bean.eventbus.BaseEventBus;
import org.greenrobot.eventbus.EventBus;
@@ -90,12 +96,12 @@
    FragmentHomePageBinding viewBinding;
    AAChartView aaChartView1;
    AAChartView aaChartView;
    AAChartModel aaChartModel1;
    AAChartView aaChartView2;
    AAChartModel aaChartModel;
    AAChartModel aaChartModel2;
    private TimeType timeType = TimeType.day;//时间类型(默认:天)
    @Override
@@ -106,30 +112,169 @@
    @Override
    public void onBindView(Bundle savedInstanceState) {
        viewBinding.homePageStationDataTitleTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setUpAAChartView(1);
        //初始化
        initView();
        //初始化界面监听器
        initEvent();
            }
        });
    private void initEvent() {
        //时间选择器
        viewBinding.yearMonthDayTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                timeSelectedView();
                selectedTimePickerBuilder();
            }
        });
        timeSelectedView();
//        viewBinding.test2.setOnClickListener(new View.OnClickListener() {
//            @Override
//            public void onClick(View v) {
//                setUpAAChartView(2);
//            }
//        });
        //日
        viewBinding.dayTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                timeType = TimeType.day;
                viewBinding.dayTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.time_selected_38c494));
                viewBinding.dayTv.setTextColor(_mActivity.getColor(R.color.text_38C494));
                viewBinding.monthTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
                viewBinding.monthTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
                viewBinding.yearTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
                viewBinding.yearTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
                viewBinding.lifeCycleTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
                viewBinding.lifeCycleTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
                viewBinding.v1.setVisibility(View.GONE);
                viewBinding.v2.setVisibility(View.VISIBLE);
                viewBinding.v3.setVisibility(View.VISIBLE);
                viewBinding.homePageStationSelectTimeLl.setVisibility(View.VISIBLE);
                setUpAAChartView(1);
    }
        });
        //月
        viewBinding.monthTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                timeType = TimeType.month;
                viewBinding.dayTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
                viewBinding.dayTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
    private  void timeSelectedView(){
                viewBinding.monthTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.time_selected_38c494));
                viewBinding.monthTv.setTextColor(_mActivity.getColor(R.color.text_38C494));
                viewBinding.yearTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
                viewBinding.yearTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
                viewBinding.lifeCycleTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
                viewBinding.lifeCycleTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
                viewBinding.v1.setVisibility(View.GONE);
                viewBinding.v2.setVisibility(View.GONE);
                viewBinding.v3.setVisibility(View.VISIBLE);
                viewBinding.homePageStationSelectTimeLl.setVisibility(View.VISIBLE);
                setUpAAChartView(2);
            }
        });
        //年
        viewBinding.yearTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                timeType = TimeType.year;
                viewBinding.dayTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
                viewBinding.dayTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
                viewBinding.monthTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
                viewBinding.monthTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
                viewBinding.yearTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.time_selected_38c494));
                viewBinding.yearTv.setTextColor(_mActivity.getColor(R.color.text_38C494));
                viewBinding.lifeCycleTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
                viewBinding.lifeCycleTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
                viewBinding.v1.setVisibility(View.VISIBLE);
                viewBinding.v2.setVisibility(View.GONE);
                viewBinding.v3.setVisibility(View.GONE);
                viewBinding.homePageStationSelectTimeLl.setVisibility(View.VISIBLE);
                setUpAAChartView(2);
            }
        });
        //生命期
        viewBinding.lifeCycleTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                timeType = TimeType.lifecycle;
                viewBinding.dayTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
                viewBinding.dayTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
                viewBinding.monthTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
                viewBinding.monthTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
                viewBinding.yearTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
                viewBinding.yearTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
                viewBinding.lifeCycleTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.time_selected_38c494));
                viewBinding.lifeCycleTv.setTextColor(_mActivity.getColor(R.color.text_38C494));
                viewBinding.v1.setVisibility(View.VISIBLE);
                viewBinding.v2.setVisibility(View.VISIBLE);
                viewBinding.v3.setVisibility(View.GONE);
                viewBinding.homePageStationSelectTimeLl.setVisibility(View.GONE);
                setUpAAChartView(2);
            }
        });
        //上一步
        viewBinding.lastStepIv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            }
        });
        //下一步
        viewBinding.nextStepIv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            }
        });
    }
    private void initView() {
        String dataStr = TimeUtils.getDateTimestamp(System.currentTimeMillis(), TimeUtils.zhDateFormat);
        if (!UserConfigManage.getInstance().isZh()) {
            dataStr = TimeUtils.getDateTimestamp(System.currentTimeMillis(), TimeUtils.enDateFormat);
        }
        viewBinding.yearMonthDayTv.setText(dataStr);
        viewBinding.dayTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.drawable.time_selected_38c494));
        viewBinding.dayTv.setTextColor(_mActivity.getColor(R.color.text_38C494));
        viewBinding.monthTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
        viewBinding.monthTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
        viewBinding.yearTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
        viewBinding.yearTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
        viewBinding.lifeCycleTv.setBackground(AppCompatResources.getDrawable(_mActivity, R.color.text_00000000));
        viewBinding.lifeCycleTv.setTextColor(_mActivity.getColor(R.color.text_40000000));
        viewBinding.v1.setVisibility(View.GONE);
        viewBinding.v2.setVisibility(View.VISIBLE);
        viewBinding.v3.setVisibility(View.VISIBLE);
        viewBinding.homePageStationSelectTimeLl.setVisibility(View.VISIBLE);
    }
    /**
     * 时间选择器
     */
    private void selectedTimePickerBuilder() {
        //时间选择器
        Calendar selectedDate = Calendar.getInstance();
        Calendar startDate = Calendar.getInstance();
@@ -147,12 +292,12 @@
//                        tvTime.setText(getTime(date));
            }
        })
                .setType(new boolean[]{true, false, false, false, false, false})// 默认全部显示
                .setCancelText("取消")//取消按钮文字
                .setSubmitText("确定")//确认按钮文字
                .setType(new boolean[]{true, true, true, false, false, false})// 默认全部显示
                .setCancelText(_mActivity.getString(R.string.loading_cancel))//取消按钮文字
                .setSubmitText(_mActivity.getString(R.string.loading_affirm))//确认按钮文字
                .setContentTextSize(18)//滚轮文字大小
                .setTitleSize(18)//标题文字大小
                .setTitleText("时间选择")//标题文字
                .setTitleText(_mActivity.getString(R.string.time_selectd))//标题文字
                .setOutSideCancelable(true)//点击屏幕,点在控件外部范围时,是否取消显示
                .isCyclic(false)//是否循环滚动
                .setTitleColor(_mActivity.getColor(R.color.text_90000000))//标题文字颜色
@@ -163,54 +308,51 @@
                .setTextColorCenter(_mActivity.getColor(R.color.text_90000000))
                .setDate(selectedDate)// 如果不设置的话,默认是系统时间*/
                .setRangDate(startDate, endDate)//起始终止年月日设定
                .setLabel("年", "月", "日", "时", "分", "秒")//默认设置为年月日时分秒
                .setLabel(_mActivity.getString(R.string.year), _mActivity.getString(R.string.month), _mActivity.getString(R.string.day), _mActivity.getString(R.string.hour), _mActivity.getString(R.string.minute), _mActivity.getString(R.string.succeed))//默认设置为年月日时分秒
                .isCenterLabel(false) //是否只显示中间选中项的label文字,false则每项item全部都带有label。
                .isDialog(false)//是否显示为对话框样式
                .setItemVisibleCount(17)//对话框高度
                .setItemVisibleCount(7)//对话框高度
                .setDividerColor(_mActivity.getColor(R.color.text_E1E1E1))
                .setDecorView(viewBinding.wwwwww)
//                        .setLineSpacingMultiplier(3f)//
                .setLineSpacingMultiplier(3f)//
                .build();
        pvTime.show();
    }
    private void setUpAAChartView(int value) {
        if (value == 1) {
            aaChartView1 = viewBinding.AAChartView;
            aaChartView1.callBack = this;
            aaChartModel1 = configureAAChartModel();
            aaChartView1.aa_drawChartWithChartModel(aaChartModel1);
            aaChartView = viewBinding.AAChartView;
            aaChartView.callBack = this;
            aaChartModel = configureAAChartModel();
            aaChartView.aa_drawChartWithChartModel(aaChartModel);
        } else if (value == 2) {
//            aaChartView2 = viewBinding.AAChartView2;
            aaChartView2.callBack = this;
            aaChartModel2 = configureColorfulColumnChart();// configureTheAAChartModel("colorfulColumnChart");
            aaChartView2.aa_drawChartWithChartModel(aaChartModel2);
            aaChartView = viewBinding.AAChartView;
            aaChartView.callBack = this;
            aaChartModel = configureColorfulColumnChart();// configureTheAAChartModel("colorfulColumnChart");
            aaChartView.aa_drawChartWithChartModel(aaChartModel);
        }
    }
    private AAChartModel configureAAChartModel() {
//        Intent intent = getIntent();
        String chartType = AAChartType.Area;
        int position = 0;
        aaChartModel1 = BasicChartComposer.configureAreaChart();
        aaChartModel = BasicChartComposer.configureAreaChart();
        configureTheStyleForDifferentTypeChart(chartType, position);
//        configureViewsVisibility(chartType);
        return aaChartModel1;
        return aaChartModel;
    }
    private void configureTheStyleForDifferentTypeChart(String chartType, int position) {
        if ((chartType.equals(AAChartType.Area) || chartType.equals(AAChartType.Line))
                && (position == 4 || position == 5)) {
            aaChartModel1 = BasicChartComposer.configureStepAreaChartAndStepLineChart();
            aaChartModel = BasicChartComposer.configureStepAreaChartAndStepLineChart();
        } else if (chartType.equals(AAChartType.Column) || chartType.equals(AAChartType.Bar)) {
            aaChartModel1 = BasicChartComposer.configureColumnChartAndBarChart();
            aaChartModel = BasicChartComposer.configureColumnChartAndBarChart();
        } else if (chartType.equals(AAChartType.Area) || chartType.equals(AAChartType.Areaspline)) {
            aaChartModel1 = BasicChartComposer.configureAreaChartAndAreasplineChartStyle(chartType);
            aaChartModel = BasicChartComposer.configureAreaChartAndAreasplineChartStyle(chartType);
        } else if (chartType.equals(AAChartType.Line) || chartType.equals(AAChartType.Spline)) {
            aaChartModel1 = BasicChartComposer.configureLineChartAndSplineChartStyle(chartType);
            aaChartModel = BasicChartComposer.configureLineChartAndSplineChartStyle(chartType);
        }
        aaChartModel1.chartType = chartType;
        aaChartModel.chartType = chartType;
    }
    private AAChartModel configureTheAAChartModel(String chartType) {
@@ -344,4 +486,12 @@
            }
        }
    }
    public enum TimeType {
        day,
        month,
        year,
        lifecycle
    }
}
app/src/main/java/com/hdl/photovoltaic/utils/TimeUtils.java
@@ -16,6 +16,9 @@
 */
public class TimeUtils {
    public static final String zhDateFormat = "yyyy/MM/dd";
    public static final String enDateFormat = "dd/MM/yyyy";
    /**
     * 获取分钟
     *
@@ -37,11 +40,12 @@
     * 时间戳转时间(格式:yyyy-MM-dd)
     *
     * @param timestamp 时间戳
     * @param date_format 时间格式(例如"yyyy-MM-dd")
     * @return -
     */
    public static String getDateTimestamp(long timestamp) {
    public static String getDateTimestamp(long timestamp, String date_format) {
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd ", Locale.ENGLISH);
            SimpleDateFormat dateFormat = new SimpleDateFormat(date_format, Locale.ENGLISH);
            Date date = new Date(timestamp);
            return dateFormat.format(date);
        } catch (Exception e) {
@@ -95,4 +99,31 @@
        return "";
    }
    /**
     * 加天数
     *
     * @param date      日历
     * @param daysToAdd 天数
     * @return 日历
     */
    public static Calendar addDaysToDate(Calendar date, int daysToAdd) {
        Calendar newDate = (Calendar) date.clone();
        newDate.add(Calendar.DAY_OF_MONTH, daysToAdd);
        return newDate;
    }
    /**
     * 减天数
     *
     * @param date           日历
     * @param daysToSubtract 天数
     * @return 日历
     */
    public static Calendar subtractDaysFromDate(Calendar date, int daysToSubtract) {
        Calendar newDate = (Calendar) date.clone();
        newDate.add(Calendar.DAY_OF_MONTH, -daysToSubtract);
        return newDate;
    }
}
app/src/main/res/drawable/time_selected_38c494.xml
New file
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/text_1238C494" />
    <corners android:radius="@dimen/dp_6" />
    <stroke
        android:width="@dimen/dp_1"
        android:color="@color/text_38C494" />
</shape>
app/src/main/res/layout/fragment_home_page.xml
@@ -45,7 +45,7 @@
            <LinearLayout
                android:id="@+id/home_page_station_ll"
                android:layout_width="0dp"
                android:layout_width="375dp"
                android:layout_height="@dimen/dp_56"
                android:orientation="horizontal"
                app:layout_constraintEnd_toEndOf="parent"
@@ -284,7 +284,7 @@
                        android:layout_width="86dp"
                        android:layout_height="match_parent"
                        android:gravity="center"
                        android:text="Day" />
                        android:text="@string/day" />
                    <View
                        android:id="@+id/v_1"
@@ -298,7 +298,7 @@
                        android:layout_width="86dp"
                        android:layout_height="match_parent"
                        android:gravity="center"
                        android:text="Month" />
                        android:text="@string/month" />
                    <View
                        android:id="@+id/v_2"
@@ -312,7 +312,7 @@
                        android:layout_width="86dp"
                        android:layout_height="match_parent"
                        android:gravity="center"
                        android:text="Year" />
                        android:text="@string/year" />
                    <View
                        android:id="@+id/v_3"
@@ -326,7 +326,7 @@
                        android:layout_width="86dp"
                        android:layout_height="match_parent"
                        android:gravity="center"
                        android:text="LifeCycle" />
                        android:text="@string/life_cycle" />
                </LinearLayout>
                <!--时间切换控件-->
@@ -461,27 +461,27 @@
                    <TextView
                        android:id="@+id/social_contribution_data_1_tv"
                        android:layout_width="wrap_content"
                        android:layout_height="13dp"
                        android:layout_height="22dp"
                        android:layout_alignParentStart="true"
                        android:layout_alignParentTop="true"
                        android:layout_marginStart="@dimen/dp_10"
                        android:layout_marginTop="@dimen/dp_16"
                        android:text="13.14"
                        android:textColor="@color/text_90000000"
                        android:textSize="@dimen/text_14"
                        android:textSize="@dimen/text_18"
                        android:textStyle="bold" />
                    <TextView
                        android:id="@+id/social_contribution_unit_1_tv"
                        android:layout_width="wrap_content"
                        android:layout_height="9dp"
                        android:layout_height="15dp"
                        android:layout_alignBottom="@+id/social_contribution_data_1_tv"
                        android:layout_marginStart="@dimen/dp_2"
                        android:layout_toEndOf="@+id/social_contribution_data_1_tv"
                        android:text="(T)"
                        android:textColor="@color/text_90000000"
                        android:textSize="@dimen/text_14"
                        android:textStyle="bold"
                        android:textSize="@dimen/text_12"
                        />
@@ -495,7 +495,7 @@
                        android:layout_marginTop="@dimen/dp_35"
                        android:text="ssss"
                        android:textColor="@color/text_40000000"
                        android:textSize="@dimen/text_14" />
                        android:textSize="@dimen/text_12" />
                </RelativeLayout>
@@ -518,27 +518,26 @@
                    <TextView
                        android:id="@+id/social_contribution_data_2_tv"
                        android:layout_width="wrap_content"
                        android:layout_height="13dp"
                        android:layout_height="22dp"
                        android:layout_alignParentStart="true"
                        android:layout_alignParentTop="true"
                        android:layout_marginStart="@dimen/dp_10"
                        android:layout_marginTop="@dimen/dp_16"
                        android:text="13.14"
                        android:textColor="@color/text_90000000"
                        android:textSize="@dimen/text_14"
                        android:textSize="@dimen/text_18"
                        android:textStyle="bold" />
                    <TextView
                        android:id="@+id/social_contribution_unit_2_tv"
                        android:layout_width="wrap_content"
                        android:layout_height="9dp"
                        android:layout_height="15dp"
                        android:layout_alignBottom="@+id/social_contribution_data_2_tv"
                        android:layout_marginStart="@dimen/dp_2"
                        android:layout_toEndOf="@+id/social_contribution_data_2_tv"
                        android:text="(T)"
                        android:textColor="@color/text_90000000"
                        android:textSize="@dimen/text_14"
                        android:textStyle="bold"
                        android:textSize="@dimen/text_12"
                        />
@@ -552,7 +551,7 @@
                        android:layout_marginTop="@dimen/dp_35"
                        android:text="ssss"
                        android:textColor="@color/text_40000000"
                        android:textSize="@dimen/text_14" />
                        android:textSize="@dimen/text_12" />
                </RelativeLayout>
@@ -575,27 +574,25 @@
                    <TextView
                        android:id="@+id/social_contribution_data_3_tv"
                        android:layout_width="wrap_content"
                        android:layout_height="13dp"
                        android:layout_alignParentStart="true"
                        android:layout_height="22dp"
                        android:layout_alignParentTop="true"
                        android:layout_marginStart="@dimen/dp_10"
                        android:layout_marginTop="@dimen/dp_16"
                        android:text="13.14"
                        android:textColor="@color/text_90000000"
                        android:textSize="@dimen/text_14"
                        android:textSize="@dimen/text_18"
                        android:textStyle="bold" />
                    <TextView
                        android:id="@+id/social_contribution_unit_3_tv"
                        android:layout_width="wrap_content"
                        android:layout_height="9dp"
                        android:layout_height="15dp"
                        android:layout_alignBottom="@+id/social_contribution_data_3_tv"
                        android:layout_marginStart="@dimen/dp_2"
                        android:layout_toEndOf="@+id/social_contribution_data_3_tv"
                        android:text="(T)"
                        android:textColor="@color/text_90000000"
                        android:textSize="@dimen/text_14"
                        android:textStyle="bold"
                        android:textSize="@dimen/text_12"
                        />
@@ -609,22 +606,14 @@
                        android:layout_marginTop="@dimen/dp_35"
                        android:text="ssss"
                        android:textColor="@color/text_40000000"
                        android:textSize="@dimen/text_14" />
                        android:textSize="@dimen/text_12" />
                </RelativeLayout>
            </androidx.constraintlayout.widget.ConstraintLayout>
            <LinearLayout
                android:id="@+id/wwwwww"
                android:layout_width="match_parent"
                android:layout_height="323dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/home_page_station_statistics_cl">
            </LinearLayout>
        </androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/layout/item_station.xml
@@ -2,8 +2,8 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/station_status_cl"
    android:layout_width="@dimen/dp_74"
    android:layout_height="@dimen/dp_56"
    android:layout_width="74dp"
    android:layout_height="56dp"
    android:background="@drawable/station_status_no_ffffff">
    <TextView
app/src/main/res/values-w1240dp/dimens.xml
File was deleted
app/src/main/res/values-w600dp/dimens.xml
File was deleted
app/src/main/res/values/colors.xml
@@ -49,6 +49,7 @@
    <color name="text_38C494">#38C494</color>
    <color name="text_C2EDDE">#C2EDDE</color>
    <color name="text_C1C1C1">#C1C1C1</color>
    <color name="text_1238C494">#1238C494</color>
</resources>
app/src/main/res/values/strings.xml
@@ -247,4 +247,13 @@
    <string name="port_conflict">应用冲突,需要关闭onpro或者其它版本的调试软件然后再重新启动这个软件试试</string>
    <string name="time_selectd">时间选择</string>
    <string name="life_cycle">生命期</string>
    <string name="year">年</string>
    <string name="month">月</string>
    <string name="day">日</string>
    <string name="hour">时</string>
    <string name="minutc">分</string>
    <string name="seconds">秒</string>
</resources>
app/src/main/res/values/themes.xml
@@ -11,13 +11,13 @@
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>
    <style name="Theme.PhotovoltaicDebug.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>
pickerview/.gitignore
New file
@@ -0,0 +1,12 @@
/build
*.idea
/.idea/libraries
/.idea/modules
/.idea/compiler.xml
/.idea/gradle.xml
/.idea/jarRepositories.xml
/.idea/misc.xml
/.idea/modules.xml
/.idea/vcs.xml
/.idea/workspace.xml
/release
pickerview/build.gradle
New file
@@ -0,0 +1,49 @@
apply plugin: 'com.android.library'
//apply plugin: 'com.novoda.bintray-release'//添加JCenter插件
android {
    compileSdkVersion 31
    defaultConfig {
        minSdkVersion 23
        targetSdkVersion 31
        versionCode 34
        versionName "4.1.9"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    lintOptions {
        abortOnError false
    }
}
//allprojects {
//    tasks.withType(Javadoc) {//兼容中文字符
//        options{
//            encoding "UTF-8"
//            charSet 'UTF-8'
//            links "http://docs.oracle.com/javase/7/docs/api"
//        }
//    }
//}
//publish {
//    userOrg = 'contrarywind'//bintray.com 用户名/组织名 user/org name
//    groupId = 'com.contrarywind'//JCenter上显示的路径 path
//    artifactId = 'Android-PickerView'//项目名称 project name
//    publishVersion = '4.1.9'//版本号 version code
//    desc = 'this is a pickerview for android'//项目描述 description
//    website = 'https://github.com/Bigkoo/Android-PickerView' //项目网址链接 link
//}
dependencies {
//    compile fileTree(include: ['*.jar'], dir: 'libs')
    api project(path: ':wheelview')
//    implementation 'com.contrarywind:wheelview:4.1.0'
    implementation 'com.android.support:support-annotations:28.0.0'
}
pickerview/proguard-rules.pro
New file
@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/Sai/Documents/software/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}
pickerview/src/androidTest/java/com/bigkoo/pickerview/ApplicationTest.java
New file
@@ -0,0 +1,13 @@
package com.bigkoo.pickerview;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
 * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
 */
public class ApplicationTest extends ApplicationTestCase<Application> {
    public ApplicationTest() {
        super(Application.class);
    }
}
pickerview/src/main/AndroidManifest.xml
New file
@@ -0,0 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.bigkoo.pickerview">
</manifest>
pickerview/src/main/java/com/bigkoo/pickerview/adapter/ArrayWheelAdapter.java
New file
@@ -0,0 +1,44 @@
package com.bigkoo.pickerview.adapter;
import com.contrarywind.adapter.WheelAdapter;
import java.util.List;
/**
 * The simple Array wheel adapter
 * @param <T> the element type
 */
public class ArrayWheelAdapter<T> implements WheelAdapter {
    // items
    private List<T> items;
    /**
     * Constructor
     * @param items the items
     */
    public ArrayWheelAdapter(List<T> items) {
        this.items = items;
    }
    @Override
    public Object getItem(int index) {
        if (index >= 0 && index < items.size()) {
            return items.get(index);
        }
        return "";
    }
    @Override
    public int getItemsCount() {
        return items.size();
    }
    @Override
    public int indexOf(Object o){
        return items.indexOf(o);
    }
}
pickerview/src/main/java/com/bigkoo/pickerview/adapter/NumericWheelAdapter.java
New file
@@ -0,0 +1,47 @@
package com.bigkoo.pickerview.adapter;
import com.contrarywind.adapter.WheelAdapter;
/**
 * Numeric Wheel adapter.
 */
public class NumericWheelAdapter implements WheelAdapter {
    private int minValue;
    private int maxValue;
    /**
     * Constructor
     * @param minValue the wheel min value
     * @param maxValue the wheel max value
     */
    public NumericWheelAdapter(int minValue, int maxValue) {
        this.minValue = minValue;
        this.maxValue = maxValue;
    }
    @Override
    public Object getItem(int index) {
        if (index >= 0 && index < getItemsCount()) {
            int value = minValue + index;
            return value;
        }
        return 0;
    }
    @Override
    public int getItemsCount() {
        return maxValue - minValue + 1;
    }
    @Override
    public int indexOf(Object o){
        try {
            return (int)o - minValue;
        } catch (Exception e) {
            return -1;
        }
    }
}
pickerview/src/main/java/com/bigkoo/pickerview/builder/OptionsPickerBuilder.java
New file
@@ -0,0 +1,292 @@
package com.bigkoo.pickerview.builder;
import android.content.Context;
import android.graphics.Typeface;
import android.view.View;
import android.view.ViewGroup;
import com.bigkoo.pickerview.configure.PickerOptions;
import com.bigkoo.pickerview.listener.CustomListener;
import com.bigkoo.pickerview.listener.OnOptionsSelectChangeListener;
import com.bigkoo.pickerview.listener.OnOptionsSelectListener;
import com.bigkoo.pickerview.view.OptionsPickerView;
import com.contrarywind.view.WheelView;
import androidx.annotation.ColorInt;
/**
 * Created by xiaosongzeem on 2018/3/20.
 */
public class OptionsPickerBuilder {
    //配置类
    private PickerOptions mPickerOptions;
    //Required
    public OptionsPickerBuilder(Context context, OnOptionsSelectListener listener) {
        mPickerOptions = new PickerOptions(PickerOptions.TYPE_PICKER_OPTIONS);
        mPickerOptions.context = context;
        mPickerOptions.optionsSelectListener = listener;
    }
    //Option
    public OptionsPickerBuilder setSubmitText(String textContentConfirm) {
        mPickerOptions.textContentConfirm = textContentConfirm;
        return this;
    }
    public OptionsPickerBuilder setCancelText(String textContentCancel) {
        mPickerOptions.textContentCancel = textContentCancel;
        return this;
    }
    public OptionsPickerBuilder setTitleText(String textContentTitle) {
        mPickerOptions.textContentTitle = textContentTitle;
        return this;
    }
    public OptionsPickerBuilder isDialog(boolean isDialog) {
        mPickerOptions.isDialog = isDialog;
        return this;
    }
    public OptionsPickerBuilder addOnCancelClickListener(View.OnClickListener cancelListener) {
        mPickerOptions.cancelListener = cancelListener;
        return this;
    }
    public OptionsPickerBuilder setSubmitColor(int textColorConfirm) {
        mPickerOptions.textColorConfirm = textColorConfirm;
        return this;
    }
    public OptionsPickerBuilder setCancelColor(int textColorCancel) {
        mPickerOptions.textColorCancel = textColorCancel;
        return this;
    }
    /**
     * {@link #setOutSideColor} instead.
     *
     * @param backgroundId color resId.
     */
    @Deprecated
    public OptionsPickerBuilder setBackgroundId(int backgroundId) {
        mPickerOptions.outSideColor = backgroundId;
        return this;
    }
    /**
     * 显示时的外部背景色颜色,默认是灰色
     *
     * @param outSideColor color resId.
     * @return
     */
    public OptionsPickerBuilder setOutSideColor(int outSideColor) {
        mPickerOptions.outSideColor = outSideColor;
        return this;
    }
    /**
     * ViewGroup 类型
     * 设置PickerView的显示容器
     *
     * @param decorView Parent View.
     * @return
     */
    public OptionsPickerBuilder setDecorView(ViewGroup decorView) {
        mPickerOptions.decorView = decorView;
        return this;
    }
    public OptionsPickerBuilder setLayoutRes(int res, CustomListener listener) {
        mPickerOptions.layoutRes = res;
        mPickerOptions.customListener = listener;
        return this;
    }
    public OptionsPickerBuilder setBgColor(int bgColorWheel) {
        mPickerOptions.bgColorWheel = bgColorWheel;
        return this;
    }
    public OptionsPickerBuilder setTitleBgColor(int bgColorTitle) {
        mPickerOptions.bgColorTitle = bgColorTitle;
        return this;
    }
    public OptionsPickerBuilder setTitleColor(int textColorTitle) {
        mPickerOptions.textColorTitle = textColorTitle;
        return this;
    }
    public OptionsPickerBuilder setSubCalSize(int textSizeSubmitCancel) {
        mPickerOptions.textSizeSubmitCancel = textSizeSubmitCancel;
        return this;
    }
    public OptionsPickerBuilder setTitleSize(int textSizeTitle) {
        mPickerOptions.textSizeTitle = textSizeTitle;
        return this;
    }
    public OptionsPickerBuilder setContentTextSize(int textSizeContent) {
        mPickerOptions.textSizeContent = textSizeContent;
        return this;
    }
    public OptionsPickerBuilder setOutSideCancelable(boolean cancelable) {
        mPickerOptions.cancelable = cancelable;
        return this;
    }
    public OptionsPickerBuilder setLabels(String label1, String label2, String label3) {
        mPickerOptions.label1 = label1;
        mPickerOptions.label2 = label2;
        mPickerOptions.label3 = label3;
        return this;
    }
    /**
     * 设置Item 的间距倍数,用于控制 Item 高度间隔
     *
     * @param lineSpacingMultiplier 浮点型,1.0-4.0f 之间有效,超过则取极值。
     */
    public OptionsPickerBuilder setLineSpacingMultiplier(float lineSpacingMultiplier) {
        mPickerOptions.lineSpacingMultiplier = lineSpacingMultiplier;
        return this;
    }
    /**
     * Set item divider line type color.
     *
     * @param dividerColor color resId.
     */
    public OptionsPickerBuilder setDividerColor(@ColorInt int dividerColor) {
        mPickerOptions.dividerColor = dividerColor;
        return this;
    }
    /**
     * Set item divider line type.
     *
     * @param dividerType enum Type {@link WheelView.DividerType}
     */
    public OptionsPickerBuilder setDividerType(WheelView.DividerType dividerType) {
        mPickerOptions.dividerType = dividerType;
        return this;
    }
    /**
     * Set the textColor of selected item.
     *
     * @param textColorCenter color res.
     */
    public OptionsPickerBuilder setTextColorCenter(int textColorCenter) {
        mPickerOptions.textColorCenter = textColorCenter;
        return this;
    }
    /**
     * Set the textColor of outside item.
     *
     * @param textColorOut color resId.
     */
    public OptionsPickerBuilder setTextColorOut(@ColorInt int textColorOut) {
        mPickerOptions.textColorOut = textColorOut;
        return this;
    }
    public OptionsPickerBuilder setTypeface(Typeface font) {
        mPickerOptions.font = font;
        return this;
    }
    public OptionsPickerBuilder setCyclic(boolean cyclic1, boolean cyclic2, boolean cyclic3) {
        mPickerOptions.cyclic1 = cyclic1;
        mPickerOptions.cyclic2 = cyclic2;
        mPickerOptions.cyclic3 = cyclic3;
        return this;
    }
    public OptionsPickerBuilder setSelectOptions(int option1) {
        mPickerOptions.option1 = option1;
        return this;
    }
    public OptionsPickerBuilder setSelectOptions(int option1, int option2) {
        mPickerOptions.option1 = option1;
        mPickerOptions.option2 = option2;
        return this;
    }
    public OptionsPickerBuilder setSelectOptions(int option1, int option2, int option3) {
        mPickerOptions.option1 = option1;
        mPickerOptions.option2 = option2;
        mPickerOptions.option3 = option3;
        return this;
    }
    public OptionsPickerBuilder setTextXOffset(int xoffset_one, int xoffset_two, int xoffset_three) {
        mPickerOptions.x_offset_one = xoffset_one;
        mPickerOptions.x_offset_two = xoffset_two;
        mPickerOptions.x_offset_three = xoffset_three;
        return this;
    }
    public OptionsPickerBuilder isCenterLabel(boolean isCenterLabel) {
        mPickerOptions.isCenterLabel = isCenterLabel;
        return this;
    }
    /**
     * 设置最大可见数目
     *
     * @param count 建议设置为 3 ~ 9之间。
     */
    public OptionsPickerBuilder setItemVisibleCount(int count) {
        mPickerOptions.itemsVisibleCount = count;
        return this;
    }
    /**
     * 透明度是否渐变
     *
     * @param isAlphaGradient true of false
     */
    public OptionsPickerBuilder isAlphaGradient(boolean isAlphaGradient) {
        mPickerOptions.isAlphaGradient = isAlphaGradient;
        return this;
    }
    /**
     * 切换选项时,是否还原第一项
     *
     * @param isRestoreItem true:还原; false: 保持上一个选项
     * @return TimePickerBuilder
     */
    public OptionsPickerBuilder isRestoreItem(boolean isRestoreItem) {
        mPickerOptions.isRestoreItem = isRestoreItem;
        return this;
    }
    /**
     * @param listener 切换item项滚动停止时,实时回调监听。
     * @return
     */
    public OptionsPickerBuilder setOptionsSelectChangeListener(OnOptionsSelectChangeListener listener) {
        mPickerOptions.optionsSelectChangeListener = listener;
        return this;
    }
    public <T> OptionsPickerView<T> build() {
        return new OptionsPickerView<>(mPickerOptions);
    }
}
pickerview/src/main/java/com/bigkoo/pickerview/builder/TimePickerBuilder.java
New file
@@ -0,0 +1,315 @@
package com.bigkoo.pickerview.builder;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import com.bigkoo.pickerview.configure.PickerOptions;
import com.bigkoo.pickerview.listener.CustomListener;
import com.bigkoo.pickerview.listener.OnTimeSelectChangeListener;
import com.bigkoo.pickerview.listener.OnTimeSelectListener;
import com.bigkoo.pickerview.view.TimePickerView;
import com.contrarywind.view.WheelView;
import java.util.Calendar;
import androidx.annotation.ColorInt;
/**
 * Created by xiaosongzeem on 2018/3/20.
 */
public class TimePickerBuilder {
    private PickerOptions mPickerOptions;
    //Required
    public TimePickerBuilder(Context context, OnTimeSelectListener listener) {
        mPickerOptions = new PickerOptions(PickerOptions.TYPE_PICKER_TIME);
        mPickerOptions.context = context;
        mPickerOptions.timeSelectListener = listener;
    }
    //Option
    public TimePickerBuilder setGravity(int gravity) {
        mPickerOptions.textGravity = gravity;
        return this;
    }
    public TimePickerBuilder addOnCancelClickListener(View.OnClickListener cancelListener) {
        mPickerOptions.cancelListener = cancelListener;
        return this;
    }
    /**
     * new boolean[]{true, true, true, false, false, false}
     * control the "year","month","day","hours","minutes","seconds " display or hide.
     * 分别控制“年”“月”“日”“时”“分”“秒”的显示或隐藏。
     *
     * @param type 布尔型数组,长度需要设置为6。
     * @return TimePickerBuilder
     */
    public TimePickerBuilder setType(boolean[] type) {
        mPickerOptions.type = type;
        return this;
    }
    public TimePickerBuilder setSubmitText(String textContentConfirm) {
        mPickerOptions.textContentConfirm = textContentConfirm;
        return this;
    }
    public TimePickerBuilder isDialog(boolean isDialog) {
        mPickerOptions.isDialog = isDialog;
        return this;
    }
    public TimePickerBuilder setCancelText(String textContentCancel) {
        mPickerOptions.textContentCancel = textContentCancel;
        return this;
    }
    public TimePickerBuilder setTitleText(String textContentTitle) {
        mPickerOptions.textContentTitle = textContentTitle;
        return this;
    }
    public TimePickerBuilder setSubmitColor(int textColorConfirm) {
        mPickerOptions.textColorConfirm = textColorConfirm;
        return this;
    }
    public TimePickerBuilder setCancelColor(int textColorCancel) {
        mPickerOptions.textColorCancel = textColorCancel;
        return this;
    }
    /**
     * ViewGroup 类型的容器
     *
     * @param decorView 选择器会被添加到此容器中
     * @return TimePickerBuilder
     */
    public TimePickerBuilder setDecorView(ViewGroup decorView) {
        mPickerOptions.decorView = decorView;
        return this;
    }
    public TimePickerBuilder setBgColor(int bgColorWheel) {
        mPickerOptions.bgColorWheel = bgColorWheel;
        return this;
    }
    public TimePickerBuilder setTitleBgColor(int bgColorTitle) {
        mPickerOptions.bgColorTitle = bgColorTitle;
        return this;
    }
    public TimePickerBuilder setTitleColor(int textColorTitle) {
        mPickerOptions.textColorTitle = textColorTitle;
        return this;
    }
    public TimePickerBuilder setSubCalSize(int textSizeSubmitCancel) {
        mPickerOptions.textSizeSubmitCancel = textSizeSubmitCancel;
        return this;
    }
    public TimePickerBuilder setTitleSize(int textSizeTitle) {
        mPickerOptions.textSizeTitle = textSizeTitle;
        return this;
    }
    public TimePickerBuilder setContentTextSize(int textSizeContent) {
        mPickerOptions.textSizeContent = textSizeContent;
        return this;
    }
    /**
     * 设置最大可见数目
     *
     * @param count suggest value: 3, 5, 7, 9
     */
    public TimePickerBuilder setItemVisibleCount(int count) {
        mPickerOptions.itemsVisibleCount = count;
        return this;
    }
    /**
     * 透明度是否渐变
     *
     * @param isAlphaGradient true of false
     */
    public TimePickerBuilder isAlphaGradient(boolean isAlphaGradient) {
        mPickerOptions.isAlphaGradient = isAlphaGradient;
        return this;
    }
    /**
     * 因为系统Calendar的月份是从0-11的,所以如果是调用Calendar的set方法来设置时间,月份的范围也要是从0-11
     *
     * @param date
     * @return TimePickerBuilder
     */
    public TimePickerBuilder setDate(Calendar date) {
        mPickerOptions.date = date;
        return this;
    }
    public TimePickerBuilder setLayoutRes(int res, CustomListener customListener) {
        mPickerOptions.layoutRes = res;
        mPickerOptions.customListener = customListener;
        return this;
    }
    /**
     * 设置起始时间
     * 因为系统Calendar的月份是从0-11的,所以如果是调用Calendar的set方法来设置时间,月份的范围也要是从0-11
     */
    public TimePickerBuilder setRangDate(Calendar startDate, Calendar endDate) {
        mPickerOptions.startDate = startDate;
        mPickerOptions.endDate = endDate;
        return this;
    }
    /**
     * 设置间距倍数,但是只能在1.0-4.0f之间
     *
     * @param lineSpacingMultiplier
     */
    public TimePickerBuilder setLineSpacingMultiplier(float lineSpacingMultiplier) {
        mPickerOptions.lineSpacingMultiplier = lineSpacingMultiplier;
        return this;
    }
    /**
     * 设置分割线的颜色
     *
     * @param dividerColor
     */
    public TimePickerBuilder setDividerColor(@ColorInt int dividerColor) {
        mPickerOptions.dividerColor = dividerColor;
        return this;
    }
    /**
     * 设置分割线的类型
     *
     * @param dividerType
     */
    public TimePickerBuilder setDividerType(WheelView.DividerType dividerType) {
        mPickerOptions.dividerType = dividerType;
        return this;
    }
    /**
     * {@link #setOutSideColor} instead.
     *
     * @param backgroundId color resId.
     */
    @Deprecated
    public TimePickerBuilder setBackgroundId(int backgroundId) {
        mPickerOptions.outSideColor = backgroundId;
        return this;
    }
    /**
     * 显示时的外部背景色颜色,默认是灰色
     *
     * @param outSideColor
     */
    public TimePickerBuilder setOutSideColor(@ColorInt int outSideColor) {
        mPickerOptions.outSideColor = outSideColor;
        return this;
    }
    /**
     * 设置分割线之间的文字的颜色
     *
     * @param textColorCenter
     */
    public TimePickerBuilder setTextColorCenter(@ColorInt int textColorCenter) {
        mPickerOptions.textColorCenter = textColorCenter;
        return this;
    }
    /**
     * 设置分割线以外文字的颜色
     *
     * @param textColorOut
     */
    public TimePickerBuilder setTextColorOut(@ColorInt int textColorOut) {
        mPickerOptions.textColorOut = textColorOut;
        return this;
    }
    public TimePickerBuilder isCyclic(boolean cyclic) {
        mPickerOptions.cyclic = cyclic;
        return this;
    }
    public TimePickerBuilder setOutSideCancelable(boolean cancelable) {
        mPickerOptions.cancelable = cancelable;
        return this;
    }
    public TimePickerBuilder setLunarCalendar(boolean lunarCalendar) {
        mPickerOptions.isLunarCalendar = lunarCalendar;
        return this;
    }
    public TimePickerBuilder setLabel(String label_year, String label_month, String label_day, String label_hours, String label_mins, String label_seconds) {
        mPickerOptions.label_year = label_year;
        mPickerOptions.label_month = label_month;
        mPickerOptions.label_day = label_day;
        mPickerOptions.label_hours = label_hours;
        mPickerOptions.label_minutes = label_mins;
        mPickerOptions.label_seconds = label_seconds;
        return this;
    }
    /**
     * 设置X轴倾斜角度[ -90 , 90°]
     *
     * @param x_offset_year    年
     * @param x_offset_month   月
     * @param x_offset_day     日
     * @param x_offset_hours   时
     * @param x_offset_minutes 分
     * @param x_offset_seconds 秒
     * @return
     */
    public TimePickerBuilder setTextXOffset(int x_offset_year, int x_offset_month, int x_offset_day,
                                            int x_offset_hours, int x_offset_minutes, int x_offset_seconds) {
        mPickerOptions.x_offset_year = x_offset_year;
        mPickerOptions.x_offset_month = x_offset_month;
        mPickerOptions.x_offset_day = x_offset_day;
        mPickerOptions.x_offset_hours = x_offset_hours;
        mPickerOptions.x_offset_minutes = x_offset_minutes;
        mPickerOptions.x_offset_seconds = x_offset_seconds;
        return this;
    }
    public TimePickerBuilder isCenterLabel(boolean isCenterLabel) {
        mPickerOptions.isCenterLabel = isCenterLabel;
        return this;
    }
    /**
     * @param listener 切换item项滚动停止时,实时回调监听。
     * @return
     */
    public TimePickerBuilder setTimeSelectChangeListener(OnTimeSelectChangeListener listener) {
        mPickerOptions.timeSelectChangeListener = listener;
        return this;
    }
    public TimePickerView build() {
        return new TimePickerView(mPickerOptions);
    }
}
pickerview/src/main/java/com/bigkoo/pickerview/configure/PickerOptions.java
New file
@@ -0,0 +1,114 @@
package com.bigkoo.pickerview.configure;
import android.content.Context;
import android.graphics.Typeface;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import com.bigkoo.pickerview.R;
import com.bigkoo.pickerview.listener.CustomListener;
import com.bigkoo.pickerview.listener.OnOptionsSelectChangeListener;
import com.bigkoo.pickerview.listener.OnOptionsSelectListener;
import com.bigkoo.pickerview.listener.OnTimeSelectChangeListener;
import com.bigkoo.pickerview.listener.OnTimeSelectListener;
import com.contrarywind.view.WheelView;
import java.util.Calendar;
/**
 * Build Options
 * Created by xiaosongzeem on 2018/3/8.
 */
public class PickerOptions {
    //constant
    private static final int PICKER_VIEW_BTN_COLOR_NORMAL = 0xFF057dff;
    private static final int PICKER_VIEW_BG_COLOR_TITLE = 0xFFf5f5f5;
    private static final int PICKER_VIEW_COLOR_TITLE = 0xFF000000;
    private static final int PICKER_VIEW_BG_COLOR_DEFAULT = 0xFFFFFFFF;
    public static final int TYPE_PICKER_OPTIONS = 1;
    public static final int TYPE_PICKER_TIME = 2;
    public OnOptionsSelectListener optionsSelectListener;
    public OnTimeSelectListener timeSelectListener;
    public View.OnClickListener cancelListener;
    public OnTimeSelectChangeListener timeSelectChangeListener;
    public OnOptionsSelectChangeListener optionsSelectChangeListener;
    public CustomListener customListener;
    //options picker
    public String label1, label2, label3;//单位字符
    public int option1, option2, option3;//默认选中项
    public int x_offset_one, x_offset_two, x_offset_three;//x轴偏移量
    public boolean cyclic1 = false;//是否循环,默认否
    public boolean cyclic2 = false;
    public boolean cyclic3 = false;
    public boolean isRestoreItem = false; //切换时,还原第一项
    //time picker
    public boolean[] type = new boolean[]{true, true, true, false, false, false};//显示类型,默认显示: 年月日
    public Calendar date;//当前选中时间
    public Calendar startDate;//开始时间
    public Calendar endDate;//终止时间
    public int startYear;//开始年份
    public int endYear;//结尾年份
    public boolean cyclic = false;//是否循环
    public boolean isLunarCalendar = false;//是否显示农历
    public String label_year, label_month, label_day, label_hours, label_minutes, label_seconds;//单位
    public int x_offset_year, x_offset_month, x_offset_day, x_offset_hours, x_offset_minutes, x_offset_seconds;//单位
    public PickerOptions(int buildType) {
        if (buildType == TYPE_PICKER_OPTIONS) {
            layoutRes = R.layout.pickerview_options;
        } else {
            layoutRes = R.layout.pickerview_time;
        }
    }
    //******* general field ******//
    public int layoutRes;
    public ViewGroup decorView;
    public int textGravity = Gravity.CENTER;
    public Context context;
    public String textContentConfirm;//确定按钮文字
    public String textContentCancel;//取消按钮文字
    public String textContentTitle;//标题文字
    public int textColorConfirm = PICKER_VIEW_BTN_COLOR_NORMAL;//确定按钮颜色
    public int textColorCancel = PICKER_VIEW_BTN_COLOR_NORMAL;//取消按钮颜色
    public int textColorTitle = PICKER_VIEW_COLOR_TITLE;//标题颜色
    public int bgColorWheel = PICKER_VIEW_BG_COLOR_DEFAULT;//滚轮背景颜色
    public int bgColorTitle = PICKER_VIEW_BG_COLOR_DEFAULT;//标题背景颜色
    public int textSizeSubmitCancel = 17;//确定取消按钮大小
    public int textSizeTitle = 18;//标题文字大小
    public int textSizeContent = 18;//内容文字大小
    public int textColorOut = 0xFFa8a8a8; //分割线以外的文字颜色
    public int textColorCenter = 0xFF2a2a2a; //分割线之间的文字颜色
    public int dividerColor = 0xFFd5d5d5; //分割线的颜色
    public int outSideColor = -1; //显示时的外部背景色颜色,默认是灰色
    public float lineSpacingMultiplier = 1.6f; // 条目间距倍数 默认1.6
    public boolean isDialog;//是否是对话框模式
    public boolean cancelable = true;//是否能取消
    public boolean isCenterLabel = false;//是否只显示中间的label,默认每个item都显示
    public Typeface font = Typeface.MONOSPACE;//字体样式
    public WheelView.DividerType dividerType = WheelView.DividerType.FILL;//分隔线类型
    public int itemsVisibleCount = 9; //最大可见条目数
    public boolean isAlphaGradient = false; //透明度渐变
}
pickerview/src/main/java/com/bigkoo/pickerview/listener/CustomListener.java
New file
@@ -0,0 +1,13 @@
package com.bigkoo.pickerview.listener;
import android.view.View;
/**
 * Created by KyuYi on 2017/3/2.
 * E-Mail:kyu_yi@sina.com
 * 功能:
 */
public interface CustomListener {
    void customLayout(View v);
}
pickerview/src/main/java/com/bigkoo/pickerview/listener/ISelectTimeCallback.java
New file
@@ -0,0 +1,10 @@
package com.bigkoo.pickerview.listener;
/**
 * Created by zengsong on 2018/3/21.
 */
public interface ISelectTimeCallback {
    public void onTimeSelectChanged();
}
pickerview/src/main/java/com/bigkoo/pickerview/listener/OnDismissListener.java
New file
@@ -0,0 +1,8 @@
package com.bigkoo.pickerview.listener;
/**
 * Created by Sai on 15/8/9.
 */
public interface OnDismissListener {
    public void onDismiss(Object o);
}
pickerview/src/main/java/com/bigkoo/pickerview/listener/OnOptionsSelectChangeListener.java
New file
@@ -0,0 +1,11 @@
package com.bigkoo.pickerview.listener;
/**
 * Created by xiaosong on 2018/3/20.
 */
public interface OnOptionsSelectChangeListener {
    void onOptionsSelectChanged(int options1, int options2, int options3);
}
pickerview/src/main/java/com/bigkoo/pickerview/listener/OnOptionsSelectListener.java
New file
@@ -0,0 +1,13 @@
package com.bigkoo.pickerview.listener;
import android.view.View;
/**
 * Created by xiaosong on 2018/3/20.
 */
public interface OnOptionsSelectListener {
    void onOptionsSelect(int options1, int options2, int options3, View v);
}
pickerview/src/main/java/com/bigkoo/pickerview/listener/OnTimeSelectChangeListener.java
New file
@@ -0,0 +1,12 @@
package com.bigkoo.pickerview.listener;
import java.util.Date;
/**
 * Created by xiaosong on 2018/3/20.
 */
public interface OnTimeSelectChangeListener {
    void onTimeSelectChanged(Date date);
}
pickerview/src/main/java/com/bigkoo/pickerview/listener/OnTimeSelectListener.java
New file
@@ -0,0 +1,14 @@
package com.bigkoo.pickerview.listener;
import android.view.View;
import java.util.Date;
/**
 * Created by xiaosong on 2018/3/20.
 */
public interface OnTimeSelectListener {
    void onTimeSelect(Date date, View v);
}
pickerview/src/main/java/com/bigkoo/pickerview/utils/ChinaDate.java
New file
@@ -0,0 +1,352 @@
package com.bigkoo.pickerview.utils;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
public class ChinaDate {
    /**
     * <lunarInfo 数组值的计算原理>
     *
     * 0x代表十六进制,后面的五位数是十六进制数。
     * 举个例子: 1980年的数据是 0x095b0
     * 二进制:  0000 1001 0101 1011 0000
     * 1-4:   表示当年是否为闰年,是的话为1,否则为0。
     * 5-16: 为除了闰月外的正常月份是大月还是小月,1为30天,0为29天。
     * 注意:  从1月到12月对应的是第16位到第5位。
     * 17-20: 非闰年为0,大于0表示闰月月份,仅当存在闰月的情况下有意义。
     */
    final private static long[] lunarInfo = new long[]{
            0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,//1900-1909
            0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,//1910-1919
            0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,//1920-1929
            0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,//1930-1939
            0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,//1940-1949
            0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,//1950-1959
            0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,//1960-1969
            0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,//1970-1979
            0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,//1980-1989
            0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,//1990-1999
            0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,//2000-2009
            0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,//2010-2019
            0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,//2020-2029
            0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,//2030-2039
            0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,//2040-2049
            0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,//2050-2059
            0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,//2060-2069
            0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,//2070-2079
            0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,//2080-2089
            0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,//2090-2099
            0x0d520};//2100
    private final static String[] nStr1 = new String[]{"", "正", "二", "三", "四",
            "五", "六", "七", "八", "九", "十", "冬", "腊"};
    private final static String[] Gan = new String[]{"甲", "乙", "丙", "丁", "戊",
            "己", "庚", "辛", "壬", "癸"};
    private final static String[] Zhi = new String[]{"子", "丑", "寅", "卯", "辰",
            "巳", "午", "未", "申", "酉", "戌", "亥"};
    private final static String[] Animals = new String[]{"鼠", "牛", "虎", "兔",
            "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"};
    /**
     * 传回农历
     *
     * @param y 年的总天数
     * @return 农历
     */
    final private static int lYearDays(int y) {
        int i, sum = 348;
        for (i = 0x8000; i > 0x8; i >>= 1) {
            if ((lunarInfo[y - 1900] & i) != 0)
                sum += 1;
        }
        return (sum + leapDays(y));
    }
    /**
     * 传回农历
     *
     * @param y 年闰月的天数
     * @return 农历
     */
    final public static int leapDays(int y) {
        if (leapMonth(y) != 0) {
            if ((lunarInfo[y - 1900] & 0x10000) != 0)
                return 30;
            else
                return 29;
        } else
            return 0;
    }
    /**
     * 传回农历
     *
     * @param y 年闰哪个月 1-12 , 没闰传回 0
     * @return 农历
     */
    final public static int leapMonth(int y) {
        return (int) (lunarInfo[y - 1900] & 0xf);
    }
    /**
     * 传回农历 y
     *
     * @param y y年m月的总天数
     * @param m y年m月的总天数
     * @return 农历
     */
    final public static int monthDays(int y, int m) {
        if ((lunarInfo[y - 1900] & (0x10000 >> m)) == 0)
            return 29;
        else
            return 30;
    }
    /**
     * 传回农历
     *
     * @param y 年的生肖
     * @return
     */
    final public static String AnimalsYear(int y) {
        return Animals[(y - 4) % 12];
    }
    /**
     * 传入
     *
     * @param num 月日的offset 传回干支,0是甲子
     * @return 干支
     */
    final private static String cyclicalm(int num) {
        return (Gan[num % 10] + Zhi[num % 12]);
    }
    /**
     * 传入 offset 传回干支
     *
     * @param y 0是甲子
     * @return 干支
     */
    final public static String cyclical(int y) {
        int num = y - 1900 + 36;
        return (cyclicalm(num));
    }
    /**
     * 传出y年m月d日对应的农历.year0 .month1 .day2 .yearCyl3 .monCyl4 .dayCyl5 .isLeap6
     *
     * @param y 年
     * @param m 月
     * @param d 日
     * @return y年m月d日对应的农历
     */
    final public static long[] calElement(int y, int m, int d) {
        long[] nongDate = new long[7];
        int i = 0, temp = 0, leap = 0;
        Date baseDate = new GregorianCalendar(0 + 1900, 0, 31).getTime();
        Date objDate = new GregorianCalendar(y, m - 1, d).getTime();
        long offset = (objDate.getTime() - baseDate.getTime()) / 86400000L;
        nongDate[5] = offset + 40;
        nongDate[4] = 14;
        for (i = 1900; i < 2100 && offset > 0; i++) {
            temp = lYearDays(i);
            offset -= temp;
            nongDate[4] += 12;
        }
        if (offset < 0) {
            offset += temp;
            i--;
            nongDate[4] -= 12;
        }
        nongDate[0] = i;
        nongDate[3] = i - 1864;
        leap = leapMonth(i); // 闰哪个月
        nongDate[6] = 0;
        for (i = 1; i < 13 && offset > 0; i++) {
            // 闰月
            if (leap > 0 && i == (leap + 1) && nongDate[6] == 0) {
                --i;
                nongDate[6] = 1;
                temp = leapDays((int) nongDate[0]);
            } else {
                temp = monthDays((int) nongDate[0], i);
            }
            // 解除闰月
            if (nongDate[6] == 1 && i == (leap + 1))
                nongDate[6] = 0;
            offset -= temp;
            if (nongDate[6] == 0)
                nongDate[4]++;
        }
        if (offset == 0 && leap > 0 && i == leap + 1) {
            if (nongDate[6] == 1) {
                nongDate[6] = 0;
            } else {
                nongDate[6] = 1;
                --i;
                --nongDate[4];
            }
        }
        if (offset < 0) {
            offset += temp;
            --i;
            --nongDate[4];
        }
        nongDate[1] = i;
        nongDate[2] = offset + 1;
        return nongDate;
    }
    public final static String getChinaDate(int day) {
        String a = "";
        if (day == 10)
            return "初十";
        if (day == 20)
            return "二十";
        if (day == 30)
            return "三十";
        int two = (int) ((day) / 10);
        if (two == 0)
            a = "初";
        if (two == 1)
            a = "十";
        if (two == 2)
            a = "廿";
        if (two == 3)
            a = "三";
        int one = (int) (day % 10);
        switch (one) {
            case 1:
                a += "一";
                break;
            case 2:
                a += "二";
                break;
            case 3:
                a += "三";
                break;
            case 4:
                a += "四";
                break;
            case 5:
                a += "五";
                break;
            case 6:
                a += "六";
                break;
            case 7:
                a += "七";
                break;
            case 8:
                a += "八";
                break;
            case 9:
                a += "九";
                break;
        }
        return a;
    }
    public static String getCurrentLunarDate() {
        Calendar today = Calendar.getInstance(Locale.SIMPLIFIED_CHINESE);
        int year = today.get(Calendar.YEAR);
        int month = today.get(Calendar.MONTH) + 1;
        int date = today.get(Calendar.DATE);
        long[] l = calElement(year, month, date);
        StringBuffer sToday = new StringBuffer();
        try {
            sToday.append(sdf.format(today.getTime()));
            sToday.append(" 农历");
            sToday.append(cyclical(year));
            sToday.append('(');
            sToday.append(AnimalsYear(year));
            sToday.append(")年");
            sToday.append(nStr1[(int) l[1]]);
            sToday.append("月");
            sToday.append(getChinaDate((int) (l[2])));
            return sToday.toString();
        } finally {
            sToday = null;
        }
    }
    public static String oneDay(int year, int month, int day) {
        //   Calendar today = Calendar.getInstance(Locale.SIMPLIFIED_CHINESE);
        long[] l = calElement(year, month, day);
        StringBuffer sToday = new StringBuffer();
        try {
            //   sToday.append(sdf.format(today.getTime()));
            sToday.append(" 农历");
            sToday.append(cyclical(year));
            sToday.append('(');
            sToday.append(AnimalsYear(year));
            sToday.append(")年");
            sToday.append(nStr1[(int) l[1]]);
            sToday.append("月");
            sToday.append(getChinaDate((int) (l[2])));
            return sToday.toString();
        } finally {
            sToday = null;
        }
    }
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy年M月d日 EEEEE");
    /**
     * @param lunarYear 农历年份
     * @return String of Ganzhi: 甲子年
     * 甲乙丙丁戊己庚辛壬癸
     * 子丑寅卯辰巳无为申酉戌亥
     */
    public static String getLunarYearText(int lunarYear) {
        return Gan[(lunarYear - 4) % 10] + Zhi[(lunarYear - 4) % 12] + "年";
    }
    public static ArrayList<String> getYears(int startYear, int endYear) {
        ArrayList<String> years = new ArrayList<>();
        for (int i = startYear; i < endYear; i++) {
            years.add(String.format("%s(%d)", getLunarYearText(i), i));
        }
        return years;
    }
    /**
     * 获取year年的所有月份
     *
     * @param year 年
     * @return 月份列表
     */
    public static ArrayList<String> getMonths(int year) {
        ArrayList<String> baseMonths = new ArrayList<>();
        for (int i = 1; i < nStr1.length; i++) {
            baseMonths.add(nStr1[i] + "月");
        }
        if (leapMonth(year) != 0) {
            baseMonths.add(leapMonth(year), "闰" + nStr1[leapMonth(year)] + "月");
        }
        return baseMonths;
    }
    /**
     * 获取每月农历显示名称
     *
     * @param maxDay 天
     * @return 名称列表
     */
    public static ArrayList<String> getLunarDays(int maxDay) {
        ArrayList<String> days = new ArrayList<>();
        for (int i = 1; i <= maxDay; i++) {
            days.add(getChinaDate(i));
        }
        return days;
    }
}
pickerview/src/main/java/com/bigkoo/pickerview/utils/LunarCalendar.java
New file
@@ -0,0 +1,429 @@
package com.bigkoo.pickerview.utils;
import android.util.Log;
import java.util.Date;
import java.util.GregorianCalendar;
/**
 * author: Jerry on 2016/7/11 11:29.
 * description:工具类,实现公农历互转
 */
public class LunarCalendar {
    /**
     * 支持转换的最小农历年份
     */
    public static final int MIN_YEAR = 1900;
    /**
     * 支持转换的最大农历年份
     */
    public static final int MAX_YEAR = 2099;
    /**
     * 公历每月前的天数
     */
    private static final int DAYS_BEFORE_MONTH[] = {0, 31, 59, 90, 120, 151, 181,
            212, 243, 273, 304, 334, 365};
    /**
     * 用来表示1900年到2099年间农历年份的相关信息,共24位bit的16进制表示,其中:
     * 1. 前4位表示该年闰哪个月;
     * 2. 5-17位表示农历年份13个月的大小月分布,0表示小,1表示大;
     * 3. 最后7位表示农历年首(正月初一)对应的公历日期。
     * <p>
     * 以2014年的数据0x955ABF为例说明:
     * 1001 0101 0101 1010 1011 1111
     * 闰九月  农历正月初一对应公历1月31号
     */
    private static final int LUNAR_INFO[] = {
            0x84B6BF,/*1900*/
            0x04AE53, 0x0A5748, 0x5526BD, 0x0D2650, 0x0D9544, 0x46AAB9, 0x056A4D, 0x09AD42, 0x24AEB6, 0x04AE4A,/*1901-1910*/
            0x6A4DBE, 0x0A4D52, 0x0D2546, 0x5D52BA, 0x0B544E, 0x0D6A43, 0x296D37, 0x095B4B, 0x749BC1, 0x049754,/*1911-1920*/
            0x0A4B48, 0x5B25BC, 0x06A550, 0x06D445, 0x4ADAB8, 0x02B64D, 0x095742, 0x2497B7, 0x04974A, 0x664B3E,/*1921-1930*/
            0x0D4A51, 0x0EA546, 0x56D4BA, 0x05AD4E, 0x02B644, 0x393738, 0x092E4B, 0x7C96BF, 0x0C9553, 0x0D4A48,/*1931-1940*/
            0x6DA53B, 0x0B554F, 0x056A45, 0x4AADB9, 0x025D4D, 0x092D42, 0x2C95B6, 0x0A954A, 0x7B4ABD, 0x06CA51,/*1941-1950*/
            0x0B5546, 0x555ABB, 0x04DA4E, 0x0A5B43, 0x352BB8, 0x052B4C, 0x8A953F, 0x0E9552, 0x06AA48, 0x6AD53C,/*1951-1960*/
            0x0AB54F, 0x04B645, 0x4A5739, 0x0A574D, 0x052642, 0x3E9335, 0x0D9549, 0x75AABE, 0x056A51, 0x096D46,/*1961-1970*/
            0x54AEBB, 0x04AD4F, 0x0A4D43, 0x4D26B7, 0x0D254B, 0x8D52BF, 0x0B5452, 0x0B6A47, 0x696D3C, 0x095B50,/*1971-1980*/
            0x049B45, 0x4A4BB9, 0x0A4B4D, 0xAB25C2, 0x06A554, 0x06D449, 0x6ADA3D, 0x0AB651, 0x095746, 0x5497BB,/*1981-1990*/
            0x04974F, 0x064B44, 0x36A537, 0x0EA54A, 0x86B2BF, 0x05AC53, 0x0AB647, 0x5936BC, 0x092E50, 0x0C9645,/*1991-2000*/
            0x4D4AB8, 0x0D4A4C, 0x0DA541, 0x25AAB6, 0x056A49, 0x7AADBD, 0x025D52, 0x092D47, 0x5C95BA, 0x0A954E,/*2001-2010*/
            0x0B4A43, 0x4B5537, 0x0AD54A, 0x955ABF, 0x04BA53, 0x0A5B48, 0x652BBC, 0x052B50, 0x0A9345, 0x474AB9,/*2011-2020*/
            0x06AA4C, 0x0AD541, 0x24DAB6, 0x04B64A, 0x6a573D, 0x0A4E51, 0x0D2646, 0x5E933A, 0x0D534D, 0x05AA43,/*2021-2030*/
            0x36B537, 0x096D4B, 0xB4AEBF, 0x04AD53, 0x0A4D48, 0x6D25BC, 0x0D254F, 0x0D5244, 0x5DAA38, 0x0B5A4C,/*2031-2040*/
            0x056D41, 0x24ADB6, 0x049B4A, 0x7A4BBE, 0x0A4B51, 0x0AA546, 0x5B52BA, 0x06D24E, 0x0ADA42, 0x355B37,/*2041-2050*/
            0x09374B, 0x8497C1, 0x049753, 0x064B48, 0x66A53C, 0x0EA54F, 0x06AA44, 0x4AB638, 0x0AAE4C, 0x092E42,/*2051-2060*/
            0x3C9735, 0x0C9649, 0x7D4ABD, 0x0D4A51, 0x0DA545, 0x55AABA, 0x056A4E, 0x0A6D43, 0x452EB7, 0x052D4B,/*2061-2070*/
            0x8A95BF, 0x0A9553, 0x0B4A47, 0x6B553B, 0x0AD54F, 0x055A45, 0x4A5D38, 0x0A5B4C, 0x052B42, 0x3A93B6,/*2071-2080*/
            0x069349, 0x7729BD, 0x06AA51, 0x0AD546, 0x54DABA, 0x04B64E, 0x0A5743, 0x452738, 0x0D264A, 0x8E933E,/*2081-2090*/
            0x0D5252, 0x0DAA47, 0x66B53B, 0x056D4F, 0x04AE45, 0x4A4EB9, 0x0A4D4C, 0x0D1541, 0x2D92B5          /*2091-2099*/
    };
    private static int[] solar_1_1 = {1887, 0xec04c, 0xec23f, 0xec435, 0xec649,
            0xec83e, 0xeca51, 0xecc46, 0xece3a, 0xed04d, 0xed242, 0xed436,
            0xed64a, 0xed83f, 0xeda53, 0xedc48, 0xede3d, 0xee050, 0xee244,
            0xee439, 0xee64d, 0xee842, 0xeea36, 0xeec4a, 0xeee3e, 0xef052,
            0xef246, 0xef43a, 0xef64e, 0xef843, 0xefa37, 0xefc4b, 0xefe41,
            0xf0054, 0xf0248, 0xf043c, 0xf0650, 0xf0845, 0xf0a38, 0xf0c4d,
            0xf0e42, 0xf1037, 0xf124a, 0xf143e, 0xf1651, 0xf1846, 0xf1a3a,
            0xf1c4e, 0xf1e44, 0xf2038, 0xf224b, 0xf243f, 0xf2653, 0xf2848,
            0xf2a3b, 0xf2c4f, 0xf2e45, 0xf3039, 0xf324d, 0xf3442, 0xf3636,
            0xf384a, 0xf3a3d, 0xf3c51, 0xf3e46, 0xf403b, 0xf424e, 0xf4443,
            0xf4638, 0xf484c, 0xf4a3f, 0xf4c52, 0xf4e48, 0xf503c, 0xf524f,
            0xf5445, 0xf5639, 0xf584d, 0xf5a42, 0xf5c35, 0xf5e49, 0xf603e,
            0xf6251, 0xf6446, 0xf663b, 0xf684f, 0xf6a43, 0xf6c37, 0xf6e4b,
            0xf703f, 0xf7252, 0xf7447, 0xf763c, 0xf7850, 0xf7a45, 0xf7c39,
            0xf7e4d, 0xf8042, 0xf8254, 0xf8449, 0xf863d, 0xf8851, 0xf8a46,
            0xf8c3b, 0xf8e4f, 0xf9044, 0xf9237, 0xf944a, 0xf963f, 0xf9853,
            0xf9a47, 0xf9c3c, 0xf9e50, 0xfa045, 0xfa238, 0xfa44c, 0xfa641,
            0xfa836, 0xfaa49, 0xfac3d, 0xfae52, 0xfb047, 0xfb23a, 0xfb44e,
            0xfb643, 0xfb837, 0xfba4a, 0xfbc3f, 0xfbe53, 0xfc048, 0xfc23c,
            0xfc450, 0xfc645, 0xfc839, 0xfca4c, 0xfcc41, 0xfce36, 0xfd04a,
            0xfd23d, 0xfd451, 0xfd646, 0xfd83a, 0xfda4d, 0xfdc43, 0xfde37,
            0xfe04b, 0xfe23f, 0xfe453, 0xfe648, 0xfe83c, 0xfea4f, 0xfec44,
            0xfee38, 0xff04c, 0xff241, 0xff436, 0xff64a, 0xff83e, 0xffa51,
            0xffc46, 0xffe3a, 0x10004e, 0x100242, 0x100437, 0x10064b, 0x100841,
            0x100a53, 0x100c48, 0x100e3c, 0x10104f, 0x101244, 0x101438,
            0x10164c, 0x101842, 0x101a35, 0x101c49, 0x101e3d, 0x102051,
            0x102245, 0x10243a, 0x10264e, 0x102843, 0x102a37, 0x102c4b,
            0x102e3f, 0x103053, 0x103247, 0x10343b, 0x10364f, 0x103845,
            0x103a38, 0x103c4c, 0x103e42, 0x104036, 0x104249, 0x10443d,
            0x104651, 0x104846, 0x104a3a, 0x104c4e, 0x104e43, 0x105038,
            0x10524a, 0x10543e, 0x105652, 0x105847, 0x105a3b, 0x105c4f,
            0x105e45, 0x106039, 0x10624c, 0x106441, 0x106635, 0x106849,
            0x106a3d, 0x106c51, 0x106e47, 0x10703c, 0x10724f, 0x107444,
            0x107638, 0x10784c, 0x107a3f, 0x107c53, 0x107e48};
    private static int[] lunar_month_days = {1887, 0x1694, 0x16aa, 0x4ad5,
            0xab6, 0xc4b7, 0x4ae, 0xa56, 0xb52a, 0x1d2a, 0xd54, 0x75aa, 0x156a,
            0x1096d, 0x95c, 0x14ae, 0xaa4d, 0x1a4c, 0x1b2a, 0x8d55, 0xad4,
            0x135a, 0x495d, 0x95c, 0xd49b, 0x149a, 0x1a4a, 0xbaa5, 0x16a8,
            0x1ad4, 0x52da, 0x12b6, 0xe937, 0x92e, 0x1496, 0xb64b, 0xd4a,
            0xda8, 0x95b5, 0x56c, 0x12ae, 0x492f, 0x92e, 0xcc96, 0x1a94,
            0x1d4a, 0xada9, 0xb5a, 0x56c, 0x726e, 0x125c, 0xf92d, 0x192a,
            0x1a94, 0xdb4a, 0x16aa, 0xad4, 0x955b, 0x4ba, 0x125a, 0x592b,
            0x152a, 0xf695, 0xd94, 0x16aa, 0xaab5, 0x9b4, 0x14b6, 0x6a57,
            0xa56, 0x1152a, 0x1d2a, 0xd54, 0xd5aa, 0x156a, 0x96c, 0x94ae,
            0x14ae, 0xa4c, 0x7d26, 0x1b2a, 0xeb55, 0xad4, 0x12da, 0xa95d,
            0x95a, 0x149a, 0x9a4d, 0x1a4a, 0x11aa5, 0x16a8, 0x16d4, 0xd2da,
            0x12b6, 0x936, 0x9497, 0x1496, 0x1564b, 0xd4a, 0xda8, 0xd5b4,
            0x156c, 0x12ae, 0xa92f, 0x92e, 0xc96, 0x6d4a, 0x1d4a, 0x10d65,
            0xb58, 0x156c, 0xb26d, 0x125c, 0x192c, 0x9a95, 0x1a94, 0x1b4a,
            0x4b55, 0xad4, 0xf55b, 0x4ba, 0x125a, 0xb92b, 0x152a, 0x1694,
            0x96aa, 0x15aa, 0x12ab5, 0x974, 0x14b6, 0xca57, 0xa56, 0x1526,
            0x8e95, 0xd54, 0x15aa, 0x49b5, 0x96c, 0xd4ae, 0x149c, 0x1a4c,
            0xbd26, 0x1aa6, 0xb54, 0x6d6a, 0x12da, 0x1695d, 0x95a, 0x149a,
            0xda4b, 0x1a4a, 0x1aa4, 0xbb54, 0x16b4, 0xada, 0x495b, 0x936,
            0xf497, 0x1496, 0x154a, 0xb6a5, 0xda4, 0x15b4, 0x6ab6, 0x126e,
            0x1092f, 0x92e, 0xc96, 0xcd4a, 0x1d4a, 0xd64, 0x956c, 0x155c,
            0x125c, 0x792e, 0x192c, 0xfa95, 0x1a94, 0x1b4a, 0xab55, 0xad4,
            0x14da, 0x8a5d, 0xa5a, 0x1152b, 0x152a, 0x1694, 0xd6aa, 0x15aa,
            0xab4, 0x94ba, 0x14b6, 0xa56, 0x7527, 0xd26, 0xee53, 0xd54, 0x15aa,
            0xa9b5, 0x96c, 0x14ae, 0x8a4e, 0x1a4c, 0x11d26, 0x1aa4, 0x1b54,
            0xcd6a, 0xada, 0x95c, 0x949d, 0x149a, 0x1a2a, 0x5b25, 0x1aa4,
            0xfb52, 0x16b4, 0xaba, 0xa95b, 0x936, 0x1496, 0x9a4b, 0x154a,
            0x136a5, 0xda4, 0x15ac};
    /**
     * 将农历日期转换为公历日期
     *
     * @param year        农历年份
     * @param month       农历月
     * @param monthDay    农历日
     * @param isLeapMonth 该月是否是闰月
     * @return 返回农历日期对应的公历日期,year0, month1, day2.
     */
    public static final int[] lunarToSolar(int year, int month, int monthDay,
                                           boolean isLeapMonth) {
        int dayOffset;
        int leapMonth;
        int i;
        if (year < MIN_YEAR || year > MAX_YEAR || month < 1 || month > 12
                || monthDay < 1 || monthDay > 30) {
            throw new IllegalArgumentException(
                    "Illegal lunar date, must be like that:\n\t" +
                            "year : 1900~2099\n\t" +
                            "month : 1~12\n\t" +
                            "day : 1~30");
        }
        dayOffset = (LUNAR_INFO[year - MIN_YEAR] & 0x001F) - 1;
        if (((LUNAR_INFO[year - MIN_YEAR] & 0x0060) >> 5) == 2)
            dayOffset += 31;
        for (i = 1; i < month; i++) {
            if ((LUNAR_INFO[year - MIN_YEAR] & (0x80000 >> (i - 1))) == 0)
                dayOffset += 29;
            else
                dayOffset += 30;
        }
        dayOffset += monthDay;
        leapMonth = (LUNAR_INFO[year - MIN_YEAR] & 0xf00000) >> 20;
        // 这一年有闰月
        if (leapMonth != 0) {
            if (month > leapMonth || (month == leapMonth && isLeapMonth)) {
                if ((LUNAR_INFO[year - MIN_YEAR] & (0x80000 >> (month - 1))) == 0)
                    dayOffset += 29;
                else
                    dayOffset += 30;
            }
        }
        if (dayOffset > 366 || (year % 4 != 0 && dayOffset > 365)) {
            year += 1;
            if (year % 4 == 1)
                dayOffset -= 366;
            else
                dayOffset -= 365;
        }
        int[] solarInfo = new int[3];
        for (i = 1; i < 13; i++) {
            int iPos = DAYS_BEFORE_MONTH[i];
            if (year % 4 == 0 && i > 2) {
                iPos += 1;
            }
            if (year % 4 == 0 && i == 2 && iPos + 1 == dayOffset) {
                solarInfo[1] = i;
                solarInfo[2] = dayOffset - 31;
                break;
            }
            if (iPos >= dayOffset) {
                solarInfo[1] = i;
                iPos = DAYS_BEFORE_MONTH[i - 1];
                if (year % 4 == 0 && i > 2) {
                    iPos += 1;
                }
                if (dayOffset > iPos)
                    solarInfo[2] = dayOffset - iPos;
                else if (dayOffset == iPos) {
                    if (year % 4 == 0 && i == 2)
                        solarInfo[2] = DAYS_BEFORE_MONTH[i] - DAYS_BEFORE_MONTH[i - 1] + 1;
                    else
                        solarInfo[2] = DAYS_BEFORE_MONTH[i] - DAYS_BEFORE_MONTH[i - 1];
                } else
                    solarInfo[2] = dayOffset;
                break;
            }
        }
        solarInfo[0] = year;
        return solarInfo;
    }
    public static final int[] solarToLunar(int year, int month, int monthDay) {
        int[] lunarDate = new int[4];
        int index = year - solar_1_1[0];
        int data = (year << 9) | (month << 5)
                | (monthDay);
        int solar11 = 0;
        if (solar_1_1[index] > data) {
            index--;
        }
        solar11 = solar_1_1[index];
        int y = getBitInt(solar11, 12, 9);
        int m = getBitInt(solar11, 4, 5);
        int d = getBitInt(solar11, 5, 0);
        long offset = solarToInt(year, month,
                monthDay) - solarToInt(y, m, d);
        int days = lunar_month_days[index];
        int leap = getBitInt(days, 4, 13);
        int lunarY = index + solar_1_1[0];
        int lunarM = 1;
        int lunarD = 1;
        offset += 1;
        for (int i = 0; i < 13; i++) {
            int dm = getBitInt(days, 1, 12 - i) == 1 ? 30 : 29;
            if (offset > dm) {
                lunarM++;
                offset -= dm;
            } else {
                break;
            }
        }
        lunarD = (int) (offset);
        lunarDate[0] = lunarY;
        lunarDate[1] = lunarM;
        boolean isLeap = false;
        if (leap != 0 && lunarM > leap) {
            lunarDate[1] = lunarM - 1;
            if (lunarM == leap + 1) {
                isLeap = true;
            }
        }
        lunarDate[2] = lunarD;
        lunarDate[3] = isLeap ? 1 : 0;
        //Log.i("----------->",year+"-"+month+"-"+monthDay+"====>"+lunarDate[0]+"-"+lunarDate[1]+"-"+lunarDate[2]+"-"+lunarDate[3]);
        return lunarDate;
    }
    /**
     * 将公历日期转换为农历日期,且标识是否是闰月
     *
     * @param year
     * @param month
     * @param monthDay
     * @return 返回公历日期对应的农历日期,year0,month1,day2,leap3
     * @deprecated 不准确
     */
    @Deprecated
    public static final int[] solarToLunarDeprecated(int year, int month, int monthDay) {
        int[] lunarDate = new int[4];
        Date baseDate = new GregorianCalendar(1900, 0, 31).getTime();
        Date objDate = new GregorianCalendar(year, month - 1, monthDay).getTime();
        int offset = (int) ((objDate.getTime() - baseDate.getTime()) / 86400000L);
        // 用offset减去每农历年的天数计算当天是农历第几天
        // iYear最终结果是农历的年份, offset是当年的第几天
        int iYear, daysOfYear = 0;
        for (iYear = MIN_YEAR; iYear <= MAX_YEAR && offset > 0; iYear++) {
            daysOfYear = daysInLunarYear(iYear);
            offset -= daysOfYear;
        }
        if (offset < 0) {
            offset += daysOfYear;
            iYear--;
        }
        // 农历年份
        lunarDate[0] = iYear;
        int leapMonth = leapMonth(iYear); // 闰哪个月,1-12
        boolean isLeap = false;
        // 用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天
        int iMonth, daysOfMonth = 0;
        for (iMonth = 1; iMonth <= 13 && offset > 0; iMonth++) {
            daysOfMonth = daysInLunarMonth(iYear, iMonth);
            offset -= daysOfMonth;
        }
        // 当前月超过闰月,要校正
        if (leapMonth != 0 && iMonth > leapMonth) {
            --iMonth;
            Log.i("----------->", year + "-" + month + "-" + monthDay + "====>" + iMonth + "-" + leapMonth);
            if (iMonth == leapMonth) {
                isLeap = true;
            }
        }
        // offset小于0时,也要校正
        if (offset < 0) {
            offset += daysOfMonth;
            --iMonth;
        }
        lunarDate[1] = iMonth;
        lunarDate[2] = offset + 1;
        lunarDate[3] = isLeap ? 1 : 0;
        //Log.i("----------->",year+"-"+month+"-"+monthDay+"====>"+lunarDate[0]+"-"+lunarDate[1]+"-"+lunarDate[2]+"-"+lunarDate[3]);
        return lunarDate;
    }
    /**
     * 传回农历year年month月的总天数
     *
     * @param year  要计算的年份
     * @param month 要计算的月
     * @return 传回天数
     */
    final public static int daysInMonth(int year, int month) {
        return daysInMonth(year, month, false);
    }
    /**
     * 传回农历year年month月的总天数
     *
     * @param year  要计算的年份
     * @param month 要计算的月
     * @param leap  当月是否是闰月
     * @return 传回天数,如果闰月是错误的,返回0.
     */
    public static final int daysInMonth(int year, int month, boolean leap) {
        int leapMonth = leapMonth(year);
        int offset = 0;
        // 如果本年有闰月且month大于闰月时,需要校正
        if (leapMonth != 0 && month > leapMonth) {
            offset = 1;
        }
        // 不考虑闰月
        if (!leap) {
            return daysInLunarMonth(year, month + offset);
        } else {
            // 传入的闰月是正确的月份
            if (leapMonth != 0 && leapMonth == month) {
                return daysInLunarMonth(year, month + 1);
            }
        }
        return 0;
    }
    /**
     * 传回农历 year年的总天数
     *
     * @param year 将要计算的年份
     * @return 返回传入年份的总天数
     */
    private static int daysInLunarYear(int year) {
        int i, sum = 348;
        if (leapMonth(year) != 0) {
            sum = 377;
        }
        int monthInfo = LUNAR_INFO[year - MIN_YEAR] & 0x0FFF80;
        for (i = 0x80000; i > 0x7; i >>= 1) {
            if ((monthInfo & i) != 0)
                sum += 1;
        }
        return sum;
    }
    /**
     * 传回农历 year年month月的总天数,总共有13个月包括闰月
     *
     * @param year  将要计算的年份
     * @param month 将要计算的月份
     * @return 传回农历 year年month月的总天数
     */
    private static int daysInLunarMonth(int year, int month) {
        if ((LUNAR_INFO[year - MIN_YEAR] & (0x100000 >> month)) == 0)
            return 29;
        else
            return 30;
    }
    /**
     * 传回农历 year年闰哪个月 1-12 , 没闰传回 0
     *
     * @param year 将要计算的年份
     * @return 传回农历 year年闰哪个月1-12, 没闰传回 0
     */
    public static int leapMonth(int year) {
        return (int) ((LUNAR_INFO[year - MIN_YEAR] & 0xF00000)) >> 20;
    }
    private static int getBitInt(int data, int length, int shift) {
        return (data & (((1 << length) - 1) << shift)) >> shift;
    }
    private static long solarToInt(int y, int m, int d) {
        m = (m + 9) % 12;
        y = y - m / 10;
        return 365 * y + y / 4 - y / 100 + y / 400 + (m * 306 + 5) / 10
                + (d - 1);
    }
}
pickerview/src/main/java/com/bigkoo/pickerview/utils/PickerViewAnimateUtil.java
New file
@@ -0,0 +1,26 @@
package com.bigkoo.pickerview.utils;
import android.view.Gravity;
import com.bigkoo.pickerview.R;
/**
 * Created by Sai on 15/8/9.
 */
public class PickerViewAnimateUtil {
    private static final int INVALID = -1;
    /**
     * Get default animation resource when not defined by the user
     *
     * @param gravity       the animGravity of the dialog
     * @param isInAnimation determine if is in or out animation. true when is is
     * @return the id of the animation resource
     */
    public static int getAnimationResource(int gravity, boolean isInAnimation) {
        switch (gravity) {
            case Gravity.BOTTOM:
                return isInAnimation ? R.anim.pickerview_slide_in_bottom : R.anim.pickerview_slide_out_bottom;
        }
        return INVALID;
    }
}
pickerview/src/main/java/com/bigkoo/pickerview/view/BasePickerView.java
New file
@@ -0,0 +1,361 @@
package com.bigkoo.pickerview.view;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import com.bigkoo.pickerview.R;
import com.bigkoo.pickerview.configure.PickerOptions;
import com.bigkoo.pickerview.listener.OnDismissListener;
import com.bigkoo.pickerview.utils.PickerViewAnimateUtil;
/**
 * Created by Sai on 15/11/22.
 * 精仿iOSPickerViewController控件
 */
public class BasePickerView {
    private Context context;
    protected ViewGroup contentContainer;
    private ViewGroup rootView;//附加View 的 根View
    private ViewGroup dialogView;//附加Dialog 的 根View
    protected PickerOptions mPickerOptions;
    private OnDismissListener onDismissListener;
    private boolean dismissing;
    private Animation outAnim;
    private Animation inAnim;
    private boolean isShowing;
    protected int animGravity = Gravity.BOTTOM;
    private Dialog mDialog;
    protected View clickView;//是通过哪个View弹出的
    private boolean isAnim = true;
    public BasePickerView(Context context) {
        this.context = context;
    }
    protected void initViews() {
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM);
        LayoutInflater layoutInflater = LayoutInflater.from(context);
        if (isDialog()) {
            //如果是对话框模式
            dialogView = (ViewGroup) layoutInflater.inflate(R.layout.layout_basepickerview, null, false);
            //设置界面的背景为透明
            dialogView.setBackgroundColor(Color.TRANSPARENT);
            //这个是真正要加载选择器的父布局
            contentContainer = (ViewGroup) dialogView.findViewById(R.id.content_container);
            //设置对话框 默认左右间距屏幕30
            params.leftMargin = 30;
            params.rightMargin = 30;
            contentContainer.setLayoutParams(params);
            //创建对话框
            createDialog();
            //给背景设置点击事件,这样当点击内容以外的地方会关闭界面
            dialogView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    dismiss();
                }
            });
        } else {
            //如果只是要显示在屏幕的下方
            //decorView是activity的根View,包含 contentView 和 titleView
            if (mPickerOptions.decorView == null) {
                mPickerOptions.decorView = (ViewGroup) ((Activity) context).getWindow().getDecorView();
            }
            //将控件添加到decorView中
            rootView = (ViewGroup) layoutInflater.inflate(R.layout.layout_basepickerview, mPickerOptions.decorView, false);
            rootView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            if (mPickerOptions.outSideColor != -1) {
                rootView.setBackgroundColor(mPickerOptions.outSideColor);
            }
            //这个是真正要加载时间选取器的父布局
            contentContainer = (ViewGroup) rootView.findViewById(R.id.content_container);
            contentContainer.setLayoutParams(params);
        }
        setKeyBackCancelable(true);
    }
    protected void initAnim() {
        inAnim = getInAnimation();
        outAnim = getOutAnimation();
    }
    protected void initEvents() {
    }
    /**
     * @param v      (是通过哪个View弹出的)
     * @param isAnim 是否显示动画效果
     */
    public void show(View v, boolean isAnim) {
        this.clickView = v;
        this.isAnim = isAnim;
        show();
    }
    public void show(boolean isAnim) {
        show(null, isAnim);
    }
    public void show(View v) {
        this.clickView = v;
        show();
    }
    /**
     * 添加View到根视图
     */
    public void show() {
        if (isDialog()) {
            showDialog();
        } else {
            if (isShowing()) {
                return;
            }
            isShowing = true;
            onAttached(rootView);
            rootView.requestFocus();
        }
    }
    /**
     * show的时候调用
     *
     * @param view 这个View
     */
    private void onAttached(View view) {
        mPickerOptions.decorView.addView(view);
        if (isAnim) {
            contentContainer.startAnimation(inAnim);
        }
    }
    /**
     * 检测该View是不是已经添加到根视图
     *
     * @return 如果视图已经存在该View返回true
     */
    public boolean isShowing() {
        if (isDialog()) {
            return false;
        } else {
            return rootView.getParent() != null || isShowing;
        }
    }
    public void dismiss() {
        if (isDialog()) {
            dismissDialog();
        } else {
            if (dismissing) {
                return;
            }
            if (isAnim) {
                //消失动画
                outAnim.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
                    }
                    @Override
                    public void onAnimationEnd(Animation animation) {
                        dismissImmediately();
                    }
                    @Override
                    public void onAnimationRepeat(Animation animation) {
                    }
                });
                contentContainer.startAnimation(outAnim);
            } else {
                dismissImmediately();
            }
            dismissing = true;
        }
    }
    public void dismissImmediately() {
        mPickerOptions.decorView.post(new Runnable() {
            @Override
            public void run() {
                //从根视图移除
                mPickerOptions.decorView.removeView(rootView);
                isShowing = false;
                dismissing = false;
                if (onDismissListener != null) {
                    onDismissListener.onDismiss(BasePickerView.this);
                }
            }
        });
    }
    private Animation getInAnimation() {
        int res = PickerViewAnimateUtil.getAnimationResource(this.animGravity, true);
        return AnimationUtils.loadAnimation(context, res);
    }
    private Animation getOutAnimation() {
        int res = PickerViewAnimateUtil.getAnimationResource(this.animGravity, false);
        return AnimationUtils.loadAnimation(context, res);
    }
    public BasePickerView setOnDismissListener(OnDismissListener onDismissListener) {
        this.onDismissListener = onDismissListener;
        return this;
    }
    public void setKeyBackCancelable(boolean isCancelable) {
        ViewGroup View;
        if (isDialog()) {
            View = dialogView;
        } else {
            View = rootView;
        }
        View.setFocusable(isCancelable);
        View.setFocusableInTouchMode(isCancelable);
        if (isCancelable) {
            View.setOnKeyListener(onKeyBackListener);
        } else {
            View.setOnKeyListener(null);
        }
    }
    private View.OnKeyListener onKeyBackListener = new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == MotionEvent.ACTION_DOWN && isShowing()) {
                dismiss();
                return true;
            }
            return false;
        }
    };
    protected BasePickerView setOutSideCancelable(boolean isCancelable) {
        if (rootView != null) {
            View view = rootView.findViewById(R.id.outmost_container);
            if (isCancelable) {
                view.setOnTouchListener(onCancelableTouchListener);
            } else {
                view.setOnTouchListener(null);
            }
        }
        return this;
    }
    /**
     * 设置对话框模式是否可以点击外部取消
     */
    public void setDialogOutSideCancelable() {
        if (mDialog != null) {
            mDialog.setCancelable(mPickerOptions.cancelable);
        }
    }
    /**
     * Called when the user touch on black overlay, in order to dismiss the dialog.
     */
    private final View.OnTouchListener onCancelableTouchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                dismiss();
            }
            return false;
        }
    };
    public View findViewById(int id) {
        return contentContainer.findViewById(id);
    }
    public void createDialog() {
        if (dialogView != null) {
            mDialog = new Dialog(context, R.style.custom_dialog2);
            mDialog.setCancelable(mPickerOptions.cancelable);//不能点外面取消,也不能点back取消
            mDialog.setContentView(dialogView);
            Window dialogWindow = mDialog.getWindow();
            if (dialogWindow != null) {
                dialogWindow.setWindowAnimations(R.style.picker_view_scale_anim);
                dialogWindow.setGravity(Gravity.CENTER);//可以改成Bottom
            }
            mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
                @Override
                public void onDismiss(DialogInterface dialog) {
                    if (onDismissListener != null) {
                        onDismissListener.onDismiss(BasePickerView.this);
                    }
                }
            });
        }
    }
    private void showDialog() {
        if (mDialog != null) {
            mDialog.show();
        }
    }
    private void dismissDialog() {
        if (mDialog != null) {
            mDialog.dismiss();
        }
    }
    public ViewGroup getDialogContainerLayout() {
        return contentContainer;
    }
    public Dialog getDialog() {
        return mDialog;
    }
    public boolean isDialog() {
        return false;
    }
}
pickerview/src/main/java/com/bigkoo/pickerview/view/OptionsPickerView.java
New file
@@ -0,0 +1,197 @@
package com.bigkoo.pickerview.view;
import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.bigkoo.pickerview.R;
import com.bigkoo.pickerview.configure.PickerOptions;
import java.util.List;
/**
 * 条件选择器
 * Created by Sai on 15/11/22.
 */
public class OptionsPickerView<T> extends BasePickerView implements View.OnClickListener {
    private WheelOptions<T> wheelOptions;
    private static final String TAG_SUBMIT = "submit";
    private static final String TAG_CANCEL = "cancel";
    public OptionsPickerView(PickerOptions pickerOptions) {
        super(pickerOptions.context);
        mPickerOptions = pickerOptions;
        initView(pickerOptions.context);
    }
    private void initView(Context context) {
        setDialogOutSideCancelable();
        initViews();
        initAnim();
        initEvents();
        if (mPickerOptions.customListener == null) {
            LayoutInflater.from(context).inflate(mPickerOptions.layoutRes, contentContainer);
            //顶部标题
            TextView tvTitle = (TextView) findViewById(R.id.tvTitle);
            RelativeLayout rv_top_bar = (RelativeLayout) findViewById(R.id.rv_topbar);
            //确定和取消按钮
            Button btnSubmit = (Button) findViewById(R.id.btnSubmit);
            Button btnCancel = (Button) findViewById(R.id.btnCancel);
            btnSubmit.setTag(TAG_SUBMIT);
            btnCancel.setTag(TAG_CANCEL);
            btnSubmit.setOnClickListener(this);
            btnCancel.setOnClickListener(this);
            //设置文字
            btnSubmit.setText(TextUtils.isEmpty(mPickerOptions.textContentConfirm) ? context.getResources().getString(R.string.pickerview_submit) : mPickerOptions.textContentConfirm);
            btnCancel.setText(TextUtils.isEmpty(mPickerOptions.textContentCancel) ? context.getResources().getString(R.string.pickerview_cancel) : mPickerOptions.textContentCancel);
            tvTitle.setText(TextUtils.isEmpty(mPickerOptions.textContentTitle) ? "" : mPickerOptions.textContentTitle);//默认为空
            //设置color
            btnSubmit.setTextColor(mPickerOptions.textColorConfirm);
            btnCancel.setTextColor(mPickerOptions.textColorCancel);
            tvTitle.setTextColor(mPickerOptions.textColorTitle);
            rv_top_bar.setBackgroundColor(mPickerOptions.bgColorTitle);
            //设置文字大小
            btnSubmit.setTextSize(mPickerOptions.textSizeSubmitCancel);
            btnCancel.setTextSize(mPickerOptions.textSizeSubmitCancel);
            tvTitle.setTextSize(mPickerOptions.textSizeTitle);
        } else {
            mPickerOptions.customListener.customLayout(LayoutInflater.from(context).inflate(mPickerOptions.layoutRes, contentContainer));
        }
        // ----滚轮布局
        final LinearLayout optionsPicker = (LinearLayout) findViewById(R.id.optionspicker);
        optionsPicker.setBackgroundColor(mPickerOptions.bgColorWheel);
        wheelOptions = new WheelOptions<>(optionsPicker, mPickerOptions.isRestoreItem);
        if (mPickerOptions.optionsSelectChangeListener != null) {
            wheelOptions.setOptionsSelectChangeListener(mPickerOptions.optionsSelectChangeListener);
        }
        wheelOptions.setTextContentSize(mPickerOptions.textSizeContent);
        wheelOptions.setItemsVisible(mPickerOptions.itemsVisibleCount);
        wheelOptions.setAlphaGradient(mPickerOptions.isAlphaGradient);
        wheelOptions.setLabels(mPickerOptions.label1, mPickerOptions.label2, mPickerOptions.label3);
        wheelOptions.setTextXOffset(mPickerOptions.x_offset_one, mPickerOptions.x_offset_two, mPickerOptions.x_offset_three);
        wheelOptions.setCyclic(mPickerOptions.cyclic1, mPickerOptions.cyclic2, mPickerOptions.cyclic3);
        wheelOptions.setTypeface(mPickerOptions.font);
        setOutSideCancelable(mPickerOptions.cancelable);
        wheelOptions.setDividerColor(mPickerOptions.dividerColor);
        wheelOptions.setDividerType(mPickerOptions.dividerType);
        wheelOptions.setLineSpacingMultiplier(mPickerOptions.lineSpacingMultiplier);
        wheelOptions.setTextColorOut(mPickerOptions.textColorOut);
        wheelOptions.setTextColorCenter(mPickerOptions.textColorCenter);
        wheelOptions.isCenterLabel(mPickerOptions.isCenterLabel);
    }
    /**
     * 动态设置标题
     *
     * @param text 标题文本内容
     */
    public void setTitleText(String text) {
        TextView tvTitle = (TextView) findViewById(R.id.tvTitle);
        if (tvTitle != null) {
            tvTitle.setText(text);
        }
    }
    /**
     * 设置默认选中项
     *
     * @param option1
     */
    public void setSelectOptions(int option1) {
        mPickerOptions.option1 = option1;
        reSetCurrentItems();
    }
    public void setSelectOptions(int option1, int option2) {
        mPickerOptions.option1 = option1;
        mPickerOptions.option2 = option2;
        reSetCurrentItems();
    }
    public void setSelectOptions(int option1, int option2, int option3) {
        mPickerOptions.option1 = option1;
        mPickerOptions.option2 = option2;
        mPickerOptions.option3 = option3;
        reSetCurrentItems();
    }
    private void reSetCurrentItems() {
        if (wheelOptions != null) {
            wheelOptions.setCurrentItems(mPickerOptions.option1, mPickerOptions.option2, mPickerOptions.option3);
        }
    }
    public void setPicker(List<T> optionsItems) {
        this.setPicker(optionsItems, null, null);
    }
    public void setPicker(List<T> options1Items, List<List<T>> options2Items) {
        this.setPicker(options1Items, options2Items, null);
    }
    public void setPicker(List<T> options1Items,
                          List<List<T>> options2Items,
                          List<List<List<T>>> options3Items) {
        wheelOptions.setPicker(options1Items, options2Items, options3Items);
        reSetCurrentItems();
    }
    //不联动情况下调用
    public void setNPicker(List<T> options1Items,
                           List<T> options2Items,
                           List<T> options3Items) {
        wheelOptions.setLinkage(false);
        wheelOptions.setNPicker(options1Items, options2Items, options3Items);
        reSetCurrentItems();
    }
    @Override
    public void onClick(View v) {
        String tag = (String) v.getTag();
        if (tag.equals(TAG_SUBMIT)) {
            returnData();
        } else if (tag.equals(TAG_CANCEL)) {
            if (mPickerOptions.cancelListener != null) {
                mPickerOptions.cancelListener.onClick(v);
            }
        }
        dismiss();
    }
    //抽离接口回调的方法
    public void returnData() {
        if (mPickerOptions.optionsSelectListener != null) {
            int[] optionsCurrentItems = wheelOptions.getCurrentItems();
            mPickerOptions.optionsSelectListener.onOptionsSelect(optionsCurrentItems[0], optionsCurrentItems[1], optionsCurrentItems[2], clickView);
        }
    }
    @Override
    public boolean isDialog() {
        return mPickerOptions.isDialog;
    }
}
pickerview/src/main/java/com/bigkoo/pickerview/view/TimePickerView.java
New file
@@ -0,0 +1,293 @@
package com.bigkoo.pickerview.view;
import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.bigkoo.pickerview.R;
import com.bigkoo.pickerview.configure.PickerOptions;
import com.bigkoo.pickerview.listener.ISelectTimeCallback;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
/**
 * 时间选择器
 * Created by Sai on 15/11/22.
 * Updated by XiaoSong on 2017-2-22.
 */
public class TimePickerView extends BasePickerView implements View.OnClickListener {
    private WheelTime wheelTime; //自定义控件
    private static final String TAG_SUBMIT = "submit";
    private static final String TAG_CANCEL = "cancel";
    public TimePickerView(PickerOptions pickerOptions) {
        super(pickerOptions.context);
        mPickerOptions = pickerOptions;
        initView(pickerOptions.context);
    }
    private void initView(Context context) {
        setDialogOutSideCancelable();
        initViews();
        initAnim();
        if (mPickerOptions.customListener == null) {
            LayoutInflater.from(context).inflate(R.layout.pickerview_time, contentContainer);
            //顶部标题
            TextView tvTitle = (TextView) findViewById(R.id.tvTitle);
            RelativeLayout rv_top_bar = (RelativeLayout) findViewById(R.id.rv_topbar);
            //确定和取消按钮
            Button btnSubmit = (Button) findViewById(R.id.btnSubmit);
            Button btnCancel = (Button) findViewById(R.id.btnCancel);
            //这样做可以阻止子控件的点击事件穿透到父控件上,从而使得父控件响应点击事件。
            tvTitle.setClickable(true);
            tvTitle.setFocusable(true);
            btnSubmit.setTag(TAG_SUBMIT);
            btnCancel.setTag(TAG_CANCEL);
            btnSubmit.setOnClickListener(this);
            btnCancel.setOnClickListener(this);
            //设置文字
            btnSubmit.setText(TextUtils.isEmpty(mPickerOptions.textContentConfirm) ? context.getResources().getString(R.string.pickerview_submit) : mPickerOptions.textContentConfirm);
            btnCancel.setText(TextUtils.isEmpty(mPickerOptions.textContentCancel) ? context.getResources().getString(R.string.pickerview_cancel) : mPickerOptions.textContentCancel);
            tvTitle.setText(TextUtils.isEmpty(mPickerOptions.textContentTitle) ? "" : mPickerOptions.textContentTitle);//默认为空
            //设置color
            btnSubmit.setTextColor(mPickerOptions.textColorConfirm);
            btnCancel.setTextColor(mPickerOptions.textColorCancel);
            tvTitle.setTextColor(mPickerOptions.textColorTitle);
            rv_top_bar.setBackgroundColor(mPickerOptions.bgColorTitle);
            //设置文字大小
            btnSubmit.setTextSize(mPickerOptions.textSizeSubmitCancel);
            btnCancel.setTextSize(mPickerOptions.textSizeSubmitCancel);
            tvTitle.setTextSize(mPickerOptions.textSizeTitle);
        } else {
            mPickerOptions.customListener.customLayout(LayoutInflater.from(context).inflate(mPickerOptions.layoutRes, contentContainer));
        }
        // 时间转轮 自定义控件
        LinearLayout timePickerView = (LinearLayout) findViewById(R.id.timepicker);
        timePickerView.setBackgroundColor(mPickerOptions.bgColorWheel);
        initWheelTime(timePickerView);
    }
    private void initWheelTime(LinearLayout timePickerView) {
        wheelTime = new WheelTime(timePickerView, mPickerOptions.type, mPickerOptions.textGravity, mPickerOptions.textSizeContent);
        if (mPickerOptions.timeSelectChangeListener != null) {
            wheelTime.setSelectChangeCallback(new ISelectTimeCallback() {
                @Override
                public void onTimeSelectChanged() {
                    try {
                        Date date = WheelTime.dateFormat.parse(wheelTime.getTime());
                        mPickerOptions.timeSelectChangeListener.onTimeSelectChanged(date);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        wheelTime.setLunarMode(mPickerOptions.isLunarCalendar);
        if (mPickerOptions.startYear != 0 && mPickerOptions.endYear != 0
                && mPickerOptions.startYear <= mPickerOptions.endYear) {
            setRange();
        }
        //若手动设置了时间范围限制
        if (mPickerOptions.startDate != null && mPickerOptions.endDate != null) {
            if (mPickerOptions.startDate.getTimeInMillis() > mPickerOptions.endDate.getTimeInMillis()) {
                throw new IllegalArgumentException("startDate can't be later than endDate");
            } else {
                setRangDate();
            }
        } else if (mPickerOptions.startDate != null) {
            if (mPickerOptions.startDate.get(Calendar.YEAR) < 1900) {
                throw new IllegalArgumentException("The startDate can not as early as 1900");
            } else {
                setRangDate();
            }
        } else if (mPickerOptions.endDate != null) {
            if (mPickerOptions.endDate.get(Calendar.YEAR) > 2100) {
                throw new IllegalArgumentException("The endDate should not be later than 2100");
            } else {
                setRangDate();
            }
        } else {//没有设置时间范围限制,则会使用默认范围。
            setRangDate();
        }
        setTime();
        wheelTime.setLabels(mPickerOptions.label_year, mPickerOptions.label_month, mPickerOptions.label_day
                , mPickerOptions.label_hours, mPickerOptions.label_minutes, mPickerOptions.label_seconds);
        wheelTime.setTextXOffset(mPickerOptions.x_offset_year, mPickerOptions.x_offset_month, mPickerOptions.x_offset_day,
                mPickerOptions.x_offset_hours, mPickerOptions.x_offset_minutes, mPickerOptions.x_offset_seconds);
        wheelTime.setItemsVisible(mPickerOptions.itemsVisibleCount);
        wheelTime.setAlphaGradient(mPickerOptions.isAlphaGradient);
        setOutSideCancelable(mPickerOptions.cancelable);
        wheelTime.setCyclic(mPickerOptions.cyclic);
        wheelTime.setDividerColor(mPickerOptions.dividerColor);
        wheelTime.setDividerType(mPickerOptions.dividerType);
        wheelTime.setLineSpacingMultiplier(mPickerOptions.lineSpacingMultiplier);
        wheelTime.setTextColorOut(mPickerOptions.textColorOut);
        wheelTime.setTextColorCenter(mPickerOptions.textColorCenter);
        wheelTime.isCenterLabel(mPickerOptions.isCenterLabel);
    }
    /**
     * 设置默认时间
     */
    public void setDate(Calendar date) {
        mPickerOptions.date = date;
        setTime();
    }
    /**
     * 设置可以选择的时间范围, 要在setTime之前调用才有效果
     */
    private void setRange() {
        wheelTime.setStartYear(mPickerOptions.startYear);
        wheelTime.setEndYear(mPickerOptions.endYear);
    }
    /**
     * 设置可以选择的时间范围, 要在setTime之前调用才有效果
     */
    private void setRangDate() {
        wheelTime.setRangDate(mPickerOptions.startDate, mPickerOptions.endDate);
        initDefaultSelectedDate();
    }
    private void initDefaultSelectedDate() {
        //如果手动设置了时间范围
        if (mPickerOptions.startDate != null && mPickerOptions.endDate != null) {
            //若默认时间未设置,或者设置的默认时间越界了,则设置默认选中时间为开始时间。
            if (mPickerOptions.date == null || mPickerOptions.date.getTimeInMillis() < mPickerOptions.startDate.getTimeInMillis()
                    || mPickerOptions.date.getTimeInMillis() > mPickerOptions.endDate.getTimeInMillis()) {
                mPickerOptions.date = mPickerOptions.startDate;
            }
        } else if (mPickerOptions.startDate != null) {
            //没有设置默认选中时间,那就拿开始时间当默认时间
            mPickerOptions.date = mPickerOptions.startDate;
        } else if (mPickerOptions.endDate != null) {
            mPickerOptions.date = mPickerOptions.endDate;
        }
    }
    /**
     * 设置选中时间,默认选中当前时间
     */
    private void setTime() {
        int year, month, day, hours, minute, seconds;
        Calendar calendar = Calendar.getInstance();
        if (mPickerOptions.date == null) {
            calendar.setTimeInMillis(System.currentTimeMillis());
            year = calendar.get(Calendar.YEAR);
            month = calendar.get(Calendar.MONTH);
            day = calendar.get(Calendar.DAY_OF_MONTH);
            hours = calendar.get(Calendar.HOUR_OF_DAY);
            minute = calendar.get(Calendar.MINUTE);
            seconds = calendar.get(Calendar.SECOND);
        } else {
            year = mPickerOptions.date.get(Calendar.YEAR);
            month = mPickerOptions.date.get(Calendar.MONTH);
            day = mPickerOptions.date.get(Calendar.DAY_OF_MONTH);
            hours = mPickerOptions.date.get(Calendar.HOUR_OF_DAY);
            minute = mPickerOptions.date.get(Calendar.MINUTE);
            seconds = mPickerOptions.date.get(Calendar.SECOND);
        }
        wheelTime.setPicker(year, month, day, hours, minute, seconds);
    }
    @Override
    public void onClick(View v) {
        String tag = (String) v.getTag();
        if (tag.equals(TAG_SUBMIT)) {
            returnData();
        } else if (tag.equals(TAG_CANCEL)) {
            if (mPickerOptions.cancelListener != null) {
                mPickerOptions.cancelListener.onClick(v);
            }
        }
        dismiss();
    }
    public void returnData() {
        if (mPickerOptions.timeSelectListener != null) {
            try {
                Date date = WheelTime.dateFormat.parse(wheelTime.getTime());
                mPickerOptions.timeSelectListener.onTimeSelect(date, clickView);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 动态设置标题
     *
     * @param text 标题文本内容
     */
    public void setTitleText(String text) {
        TextView tvTitle = (TextView) findViewById(R.id.tvTitle);
        if (tvTitle != null) {
            tvTitle.setText(text);
        }
    }
    /**
     * 目前暂时只支持设置1900 - 2100年
     *
     * @param lunar 农历的开关
     */
    public void setLunarCalendar(boolean lunar) {
        try {
            int year, month, day, hours, minute, seconds;
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(WheelTime.dateFormat.parse(wheelTime.getTime()));
            year = calendar.get(Calendar.YEAR);
            month = calendar.get(Calendar.MONTH);
            day = calendar.get(Calendar.DAY_OF_MONTH);
            hours = calendar.get(Calendar.HOUR_OF_DAY);
            minute = calendar.get(Calendar.MINUTE);
            seconds = calendar.get(Calendar.SECOND);
            wheelTime.setLunarMode(lunar);
            wheelTime.setLabels(mPickerOptions.label_year, mPickerOptions.label_month, mPickerOptions.label_day,
                    mPickerOptions.label_hours, mPickerOptions.label_minutes, mPickerOptions.label_seconds);
            wheelTime.setPicker(year, month, day, hours, minute, seconds);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
    public boolean isLunarCalendar() {
        return wheelTime.isLunarMode();
    }
    @Override
    public boolean isDialog() {
        return mPickerOptions.isDialog;
    }
}
pickerview/src/main/java/com/bigkoo/pickerview/view/WheelOptions.java
New file
@@ -0,0 +1,428 @@
package com.bigkoo.pickerview.view;
import android.graphics.Typeface;
import android.view.View;
import com.bigkoo.pickerview.R;
import com.bigkoo.pickerview.adapter.ArrayWheelAdapter;
import com.bigkoo.pickerview.listener.OnOptionsSelectChangeListener;
import com.contrarywind.listener.OnItemSelectedListener;
import com.contrarywind.view.WheelView;
import java.util.List;
public class WheelOptions<T> {
    private View view;
    private WheelView wv_option1;
    private WheelView wv_option2;
    private WheelView wv_option3;
    private List<T> mOptions1Items;
    private List<List<T>> mOptions2Items;
    private List<List<List<T>>> mOptions3Items;
    private boolean linkage = true;//默认联动
    private boolean isRestoreItem; //切换时,还原第一项
    private OnItemSelectedListener wheelListener_option1;
    private OnItemSelectedListener wheelListener_option2;
    private OnOptionsSelectChangeListener optionsSelectChangeListener;
    public View getView() {
        return view;
    }
    public void setView(View view) {
        this.view = view;
    }
    public WheelOptions(View view, boolean isRestoreItem) {
        super();
        this.isRestoreItem = isRestoreItem;
        this.view = view;
        wv_option1 = (WheelView) view.findViewById(R.id.options1);// 初始化时显示的数据
        wv_option2 = (WheelView) view.findViewById(R.id.options2);
        wv_option3 = (WheelView) view.findViewById(R.id.options3);
    }
    public void setPicker(List<T> options1Items,
                          List<List<T>> options2Items,
                          List<List<List<T>>> options3Items) {
        this.mOptions1Items = options1Items;
        this.mOptions2Items = options2Items;
        this.mOptions3Items = options3Items;
        // 选项1
        wv_option1.setAdapter(new ArrayWheelAdapter(mOptions1Items));// 设置显示数据
        wv_option1.setCurrentItem(0);// 初始化时显示的数据
        // 选项2
        if (mOptions2Items != null) {
            wv_option2.setAdapter(new ArrayWheelAdapter(mOptions2Items.get(0)));// 设置显示数据
        }
        wv_option2.setCurrentItem(wv_option2.getCurrentItem());// 初始化时显示的数据
        // 选项3
        if (mOptions3Items != null) {
            wv_option3.setAdapter(new ArrayWheelAdapter(mOptions3Items.get(0).get(0)));// 设置显示数据
        }
        wv_option3.setCurrentItem(wv_option3.getCurrentItem());
        wv_option1.setIsOptions(true);
        wv_option2.setIsOptions(true);
        wv_option3.setIsOptions(true);
        if (this.mOptions2Items == null) {
            wv_option2.setVisibility(View.GONE);
        } else {
            wv_option2.setVisibility(View.VISIBLE);
        }
        if (this.mOptions3Items == null) {
            wv_option3.setVisibility(View.GONE);
        } else {
            wv_option3.setVisibility(View.VISIBLE);
        }
        // 联动监听器
        wheelListener_option1 = new OnItemSelectedListener() {
            @Override
            public void onItemSelected(int index) {
                int opt2Select = 0;
                if (mOptions2Items == null) {//只有1级联动数据
                    if (optionsSelectChangeListener != null) {
                        optionsSelectChangeListener.onOptionsSelectChanged(wv_option1.getCurrentItem(), 0, 0);
                    }
                } else {
                    if (!isRestoreItem) {
                        opt2Select = wv_option2.getCurrentItem();//上一个opt2的选中位置
                        //新opt2的位置,判断如果旧位置没有超过数据范围,则沿用旧位置,否则选中最后一项
                        opt2Select = opt2Select >= mOptions2Items.get(index).size() - 1 ? mOptions2Items.get(index).size() - 1 : opt2Select;
                    }
                    wv_option2.setAdapter(new ArrayWheelAdapter(mOptions2Items.get(index)));
                    wv_option2.setCurrentItem(opt2Select);
                    if (mOptions3Items != null) {
                        wheelListener_option2.onItemSelected(opt2Select);
                    } else {//只有2级联动数据,滑动第1项回调
                        if (optionsSelectChangeListener != null) {
                            optionsSelectChangeListener.onOptionsSelectChanged(index, opt2Select, 0);
                        }
                    }
                }
            }
        };
        wheelListener_option2 = new OnItemSelectedListener() {
            @Override
            public void onItemSelected(int index) {
                if (mOptions3Items != null) {
                    int opt1Select = wv_option1.getCurrentItem();
                    opt1Select = opt1Select >= mOptions3Items.size() - 1 ? mOptions3Items.size() - 1 : opt1Select;
                    index = index >= mOptions2Items.get(opt1Select).size() - 1 ? mOptions2Items.get(opt1Select).size() - 1 : index;
                    int opt3 = 0;
                    if (!isRestoreItem) {
                        // wv_option3.getCurrentItem() 上一个opt3的选中位置
                        //新opt3的位置,判断如果旧位置没有超过数据范围,则沿用旧位置,否则选中最后一项
                        opt3 = wv_option3.getCurrentItem() >= mOptions3Items.get(opt1Select).get(index).size() - 1 ?
                                mOptions3Items.get(opt1Select).get(index).size() - 1 : wv_option3.getCurrentItem();
                    }
                    wv_option3.setAdapter(new ArrayWheelAdapter(mOptions3Items.get(wv_option1.getCurrentItem()).get(index)));
                    wv_option3.setCurrentItem(opt3);
                    //3级联动数据实时回调
                    if (optionsSelectChangeListener != null) {
                        optionsSelectChangeListener.onOptionsSelectChanged(wv_option1.getCurrentItem(), index, opt3);
                    }
                } else {//只有2级联动数据,滑动第2项回调
                    if (optionsSelectChangeListener != null) {
                        optionsSelectChangeListener.onOptionsSelectChanged(wv_option1.getCurrentItem(), index, 0);
                    }
                }
            }
        };
        // 添加联动监听
        if (options1Items != null && linkage) {
            wv_option1.setOnItemSelectedListener(wheelListener_option1);
        }
        if (options2Items != null && linkage) {
            wv_option2.setOnItemSelectedListener(wheelListener_option2);
        }
        if (options3Items != null && linkage && optionsSelectChangeListener != null) {
            wv_option3.setOnItemSelectedListener(new OnItemSelectedListener() {
                @Override
                public void onItemSelected(int index) {
                    optionsSelectChangeListener.onOptionsSelectChanged(wv_option1.getCurrentItem(), wv_option2.getCurrentItem(), index);
                }
            });
        }
    }
    //不联动情况下
    public void setNPicker(List<T> options1Items, List<T> options2Items, List<T> options3Items) {
        // 选项1
        wv_option1.setAdapter(new ArrayWheelAdapter<>(options1Items));// 设置显示数据
        wv_option1.setCurrentItem(0);// 初始化时显示的数据
        // 选项2
        if (options2Items != null) {
            wv_option2.setAdapter(new ArrayWheelAdapter<>(options2Items));// 设置显示数据
        }
        wv_option2.setCurrentItem(wv_option2.getCurrentItem());// 初始化时显示的数据
        // 选项3
        if (options3Items != null) {
            wv_option3.setAdapter(new ArrayWheelAdapter<>(options3Items));// 设置显示数据
        }
        wv_option3.setCurrentItem(wv_option3.getCurrentItem());
        wv_option1.setIsOptions(true);
        wv_option2.setIsOptions(true);
        wv_option3.setIsOptions(true);
        if (optionsSelectChangeListener != null) {
            wv_option1.setOnItemSelectedListener(new OnItemSelectedListener() {
                @Override
                public void onItemSelected(int index) {
                    optionsSelectChangeListener.onOptionsSelectChanged(index, wv_option2.getCurrentItem(), wv_option3.getCurrentItem());
                }
            });
        }
        if (options2Items == null) {
            wv_option2.setVisibility(View.GONE);
        } else {
            wv_option2.setVisibility(View.VISIBLE);
            if (optionsSelectChangeListener != null) {
                wv_option2.setOnItemSelectedListener(new OnItemSelectedListener() {
                    @Override
                    public void onItemSelected(int index) {
                        optionsSelectChangeListener.onOptionsSelectChanged(wv_option1.getCurrentItem(), index, wv_option3.getCurrentItem());
                    }
                });
            }
        }
        if (options3Items == null) {
            wv_option3.setVisibility(View.GONE);
        } else {
            wv_option3.setVisibility(View.VISIBLE);
            if (optionsSelectChangeListener != null) {
                wv_option3.setOnItemSelectedListener(new OnItemSelectedListener() {
                    @Override
                    public void onItemSelected(int index) {
                        optionsSelectChangeListener.onOptionsSelectChanged(wv_option1.getCurrentItem(), wv_option2.getCurrentItem(), index);
                    }
                });
            }
        }
    }
    public void setTextContentSize(int textSize) {
        wv_option1.setTextSize(textSize);
        wv_option2.setTextSize(textSize);
        wv_option3.setTextSize(textSize);
    }
    private void setLineSpacingMultiplier() {
    }
    /**
     * 设置选项的单位
     *
     * @param label1 单位
     * @param label2 单位
     * @param label3 单位
     */
    public void setLabels(String label1, String label2, String label3) {
        if (label1 != null) {
            wv_option1.setLabel(label1);
        }
        if (label2 != null) {
            wv_option2.setLabel(label2);
        }
        if (label3 != null) {
            wv_option3.setLabel(label3);
        }
    }
    /**
     * 设置x轴偏移量
     */
    public void setTextXOffset(int x_offset_one, int x_offset_two, int x_offset_three) {
        wv_option1.setTextXOffset(x_offset_one);
        wv_option2.setTextXOffset(x_offset_two);
        wv_option3.setTextXOffset(x_offset_three);
    }
    /**
     * 设置是否循环滚动
     *
     * @param cyclic 是否循环
     */
    public void setCyclic(boolean cyclic) {
        wv_option1.setCyclic(cyclic);
        wv_option2.setCyclic(cyclic);
        wv_option3.setCyclic(cyclic);
    }
    /**
     * 设置字体样式
     *
     * @param font 系统提供的几种样式
     */
    public void setTypeface(Typeface font) {
        wv_option1.setTypeface(font);
        wv_option2.setTypeface(font);
        wv_option3.setTypeface(font);
    }
    /**
     * 分别设置第一二三级是否循环滚动
     *
     * @param cyclic1,cyclic2,cyclic3 是否循环
     */
    public void setCyclic(boolean cyclic1, boolean cyclic2, boolean cyclic3) {
        wv_option1.setCyclic(cyclic1);
        wv_option2.setCyclic(cyclic2);
        wv_option3.setCyclic(cyclic3);
    }
    /**
     * 返回当前选中的结果对应的位置数组 因为支持三级联动效果,分三个级别索引,0,1,2。
     * 在快速滑动未停止时,点击确定按钮,会进行判断,如果匹配数据越界,则设为0,防止index出错导致崩溃。
     *
     * @return 索引数组
     */
    public int[] getCurrentItems() {
        int[] currentItems = new int[3];
        currentItems[0] = wv_option1.getCurrentItem();
        if (mOptions2Items != null && mOptions2Items.size() > 0) {//非空判断
            currentItems[1] = wv_option2.getCurrentItem() > (mOptions2Items.get(currentItems[0]).size() - 1) ? 0 : wv_option2.getCurrentItem();
        } else {
            currentItems[1] = wv_option2.getCurrentItem();
        }
        if (mOptions3Items != null && mOptions3Items.size() > 0) {//非空判断
            currentItems[2] = wv_option3.getCurrentItem() > (mOptions3Items.get(currentItems[0]).get(currentItems[1]).size() - 1) ? 0 : wv_option3.getCurrentItem();
        } else {
            currentItems[2] = wv_option3.getCurrentItem();
        }
        return currentItems;
    }
    public void setCurrentItems(int option1, int option2, int option3) {
        if (linkage) {
            itemSelected(option1, option2, option3);
        } else {
            wv_option1.setCurrentItem(option1);
            wv_option2.setCurrentItem(option2);
            wv_option3.setCurrentItem(option3);
        }
    }
    private void itemSelected(int opt1Select, int opt2Select, int opt3Select) {
        if (mOptions1Items != null) {
            wv_option1.setCurrentItem(opt1Select);
        }
        if (mOptions2Items != null) {
            wv_option2.setAdapter(new ArrayWheelAdapter(mOptions2Items.get(opt1Select)));
            wv_option2.setCurrentItem(opt2Select);
        }
        if (mOptions3Items != null) {
            wv_option3.setAdapter(new ArrayWheelAdapter(mOptions3Items.get(opt1Select).get(opt2Select)));
            wv_option3.setCurrentItem(opt3Select);
        }
    }
    /**
     * 设置间距倍数,但是只能在1.2-4.0f之间
     *
     * @param lineSpacingMultiplier
     */
    public void setLineSpacingMultiplier(float lineSpacingMultiplier) {
        wv_option1.setLineSpacingMultiplier(lineSpacingMultiplier);
        wv_option2.setLineSpacingMultiplier(lineSpacingMultiplier);
        wv_option3.setLineSpacingMultiplier(lineSpacingMultiplier);
    }
    /**
     * 设置分割线的颜色
     *
     * @param dividerColor
     */
    public void setDividerColor(int dividerColor) {
        wv_option1.setDividerColor(dividerColor);
        wv_option2.setDividerColor(dividerColor);
        wv_option3.setDividerColor(dividerColor);
    }
    /**
     * 设置分割线的类型
     *
     * @param dividerType
     */
    public void setDividerType(WheelView.DividerType dividerType) {
        wv_option1.setDividerType(dividerType);
        wv_option2.setDividerType(dividerType);
        wv_option3.setDividerType(dividerType);
    }
    /**
     * 设置分割线之间的文字的颜色
     *
     * @param textColorCenter
     */
    public void setTextColorCenter(int textColorCenter) {
        wv_option1.setTextColorCenter(textColorCenter);
        wv_option2.setTextColorCenter(textColorCenter);
        wv_option3.setTextColorCenter(textColorCenter);
    }
    /**
     * 设置分割线以外文字的颜色
     *
     * @param textColorOut
     */
    public void setTextColorOut(int textColorOut) {
        wv_option1.setTextColorOut(textColorOut);
        wv_option2.setTextColorOut(textColorOut);
        wv_option3.setTextColorOut(textColorOut);
    }
    /**
     * Label 是否只显示中间选中项的
     *
     * @param isCenterLabel
     */
    public void isCenterLabel(boolean isCenterLabel) {
        wv_option1.isCenterLabel(isCenterLabel);
        wv_option2.isCenterLabel(isCenterLabel);
        wv_option3.isCenterLabel(isCenterLabel);
    }
    public void setOptionsSelectChangeListener(OnOptionsSelectChangeListener optionsSelectChangeListener) {
        this.optionsSelectChangeListener = optionsSelectChangeListener;
    }
    public void setLinkage(boolean linkage) {
        this.linkage = linkage;
    }
    /**
     * 设置最大可见数目
     *
     * @param itemsVisible 建议设置为 3 ~ 9之间。
     */
    public void setItemsVisible(int itemsVisible) {
        wv_option1.setItemsVisibleCount(itemsVisible);
        wv_option2.setItemsVisibleCount(itemsVisible);
        wv_option3.setItemsVisibleCount(itemsVisible);
    }
    public void setAlphaGradient(boolean isAlphaGradient) {
        wv_option1.setAlphaGradient(isAlphaGradient);
        wv_option2.setAlphaGradient(isAlphaGradient);
        wv_option3.setAlphaGradient(isAlphaGradient);
    }
}
pickerview/src/main/java/com/bigkoo/pickerview/view/WheelTime.java
New file
@@ -0,0 +1,900 @@
package com.bigkoo.pickerview.view;
import android.view.View;
import com.bigkoo.pickerview.R;
import com.bigkoo.pickerview.adapter.ArrayWheelAdapter;
import com.bigkoo.pickerview.adapter.NumericWheelAdapter;
import com.bigkoo.pickerview.listener.ISelectTimeCallback;
import com.bigkoo.pickerview.utils.ChinaDate;
import com.bigkoo.pickerview.utils.LunarCalendar;
import com.contrarywind.listener.OnItemSelectedListener;
import com.contrarywind.view.WheelView;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
public class WheelTime {
    public static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private View view;
    private WheelView wv_year;
    private WheelView wv_month;
    private WheelView wv_day;
    private WheelView wv_hours;
    private WheelView wv_minutes;
    private WheelView wv_seconds;
    private int gravity;
    private boolean[] type;
    private static final int DEFAULT_START_YEAR = 1900;
    private static final int DEFAULT_END_YEAR = 2100;
    private static final int DEFAULT_START_MONTH = 1;
    private static final int DEFAULT_END_MONTH = 12;
    private static final int DEFAULT_START_DAY = 1;
    private static final int DEFAULT_END_DAY = 31;
    private int startYear = DEFAULT_START_YEAR;
    private int endYear = DEFAULT_END_YEAR;
    private int startMonth = DEFAULT_START_MONTH;
    private int endMonth = DEFAULT_END_MONTH;
    private int startDay = DEFAULT_START_DAY;
    private int endDay = DEFAULT_END_DAY; //表示31天的
    private int currentYear;
    private int textSize;
    private boolean isLunarCalendar = false;
    private ISelectTimeCallback mSelectChangeCallback;
    public WheelTime(View view, boolean[] type, int gravity, int textSize) {
        super();
        this.view = view;
        this.type = type;
        this.gravity = gravity;
        this.textSize = textSize;
    }
    public void setLunarMode(boolean isLunarCalendar) {
        this.isLunarCalendar = isLunarCalendar;
    }
    public boolean isLunarMode() {
        return isLunarCalendar;
    }
    public void setPicker(int year, int month, int day) {
        this.setPicker(year, month, day, 0, 0, 0);
    }
    public void setPicker(int year, final int month, int day, int h, int m, int s) {
        if (isLunarCalendar) {
            int[] lunar = LunarCalendar.solarToLunar(year, month + 1, day);
            setLunar(lunar[0], lunar[1] - 1, lunar[2], lunar[3] == 1, h, m, s);
        } else {
            setSolar(year, month, day, h, m, s);
        }
    }
    /**
     * 设置农历
     *
     * @param year
     * @param month
     * @param day
     * @param h
     * @param m
     * @param s
     */
    private void setLunar(int year, final int month, int day, boolean isLeap, int h, int m, int s) {
        // 年
        wv_year = (WheelView) view.findViewById(R.id.year);
        wv_year.setAdapter(new ArrayWheelAdapter(ChinaDate.getYears(startYear, endYear)));// 设置"年"的显示数据
        wv_year.setLabel("");// 添加文字
        wv_year.setCurrentItem(year - startYear);// 初始化时显示的数据
        wv_year.setGravity(gravity);
        // 月
        wv_month = (WheelView) view.findViewById(R.id.month);
        wv_month.setAdapter(new ArrayWheelAdapter(ChinaDate.getMonths(year)));
        wv_month.setLabel("");
        int leapMonth = ChinaDate.leapMonth(year);
        if (leapMonth != 0 && (month > leapMonth - 1 || isLeap)) { //选中月是闰月或大于闰月
            wv_month.setCurrentItem(month + 1);
        } else {
            wv_month.setCurrentItem(month);
        }
        wv_month.setGravity(gravity);
        // 日
        wv_day = (WheelView) view.findViewById(R.id.day);
        // 判断大小月及是否闰年,用来确定"日"的数据
        if (ChinaDate.leapMonth(year) == 0) {
            wv_day.setAdapter(new ArrayWheelAdapter(ChinaDate.getLunarDays(ChinaDate.monthDays(year, month))));
        } else {
            wv_day.setAdapter(new ArrayWheelAdapter(ChinaDate.getLunarDays(ChinaDate.leapDays(year))));
        }
        wv_day.setLabel("");
        wv_day.setCurrentItem(day - 1);
        wv_day.setGravity(gravity);
        wv_hours = (WheelView) view.findViewById(R.id.hour);
        wv_hours.setAdapter(new NumericWheelAdapter(0, 23));
        //wv_hours.setLabel(context.getString(R.string.pickerview_hours));// 添加文字
        wv_hours.setCurrentItem(h);
        wv_hours.setGravity(gravity);
        wv_minutes = (WheelView) view.findViewById(R.id.min);
        wv_minutes.setAdapter(new NumericWheelAdapter(0, 59));
        //wv_minutes.setLabel(context.getString(R.string.pickerview_minutes));// 添加文字
        wv_minutes.setCurrentItem(m);
        wv_minutes.setGravity(gravity);
        wv_seconds = (WheelView) view.findViewById(R.id.second);
        wv_seconds.setAdapter(new NumericWheelAdapter(0, 59));
        //wv_seconds.setLabel(context.getString(R.string.pickerview_minutes));// 添加文字
        wv_seconds.setCurrentItem(m);
        wv_seconds.setGravity(gravity);
        // 添加"年"监听
        wv_year.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(int index) {
                int year_num = index + startYear;
                // 判断是不是闰年,来确定月和日的选择
                wv_month.setAdapter(new ArrayWheelAdapter(ChinaDate.getMonths(year_num)));
                if (ChinaDate.leapMonth(year_num) != 0 && wv_month.getCurrentItem() > ChinaDate.leapMonth(year_num) - 1) {
                    wv_month.setCurrentItem(wv_month.getCurrentItem() + 1);
                } else {
                    wv_month.setCurrentItem(wv_month.getCurrentItem());
                }
                int currentIndex = wv_day.getCurrentItem();
                int maxItem = 29;
                if (ChinaDate.leapMonth(year_num) != 0 && wv_month.getCurrentItem() > ChinaDate.leapMonth(year_num) - 1) {
                    if (wv_month.getCurrentItem() == ChinaDate.leapMonth(year_num) + 1) {
                        wv_day.setAdapter(new ArrayWheelAdapter(ChinaDate.getLunarDays(ChinaDate.leapDays(year_num))));
                        maxItem = ChinaDate.leapDays(year_num);
                    } else {
                        wv_day.setAdapter(new ArrayWheelAdapter(ChinaDate.getLunarDays(ChinaDate.monthDays(year_num, wv_month.getCurrentItem()))));
                        maxItem = ChinaDate.monthDays(year_num, wv_month.getCurrentItem());
                    }
                } else {
                    wv_day.setAdapter(new ArrayWheelAdapter(ChinaDate.getLunarDays(ChinaDate.monthDays(year_num, wv_month.getCurrentItem() + 1))));
                    maxItem = ChinaDate.monthDays(year_num, wv_month.getCurrentItem() + 1);
                }
                if (currentIndex > maxItem - 1) {
                    wv_day.setCurrentItem(maxItem - 1);
                }
                if (mSelectChangeCallback != null) {
                    mSelectChangeCallback.onTimeSelectChanged();
                }
            }
        });
        // 添加"月"监听
        wv_month.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(int index) {
                int month_num = index;
                int year_num = wv_year.getCurrentItem() + startYear;
                int currentIndex = wv_day.getCurrentItem();
                int maxItem = 29;
                if (ChinaDate.leapMonth(year_num) != 0 && month_num > ChinaDate.leapMonth(year_num) - 1) {
                    if (wv_month.getCurrentItem() == ChinaDate.leapMonth(year_num) + 1) {
                        wv_day.setAdapter(new ArrayWheelAdapter(ChinaDate.getLunarDays(ChinaDate.leapDays(year_num))));
                        maxItem = ChinaDate.leapDays(year_num);
                    } else {
                        wv_day.setAdapter(new ArrayWheelAdapter(ChinaDate.getLunarDays(ChinaDate.monthDays(year_num, month_num))));
                        maxItem = ChinaDate.monthDays(year_num, month_num);
                    }
                } else {
                    wv_day.setAdapter(new ArrayWheelAdapter(ChinaDate.getLunarDays(ChinaDate.monthDays(year_num, month_num + 1))));
                    maxItem = ChinaDate.monthDays(year_num, month_num + 1);
                }
                if (currentIndex > maxItem - 1) {
                    wv_day.setCurrentItem(maxItem - 1);
                }
                if (mSelectChangeCallback != null) {
                    mSelectChangeCallback.onTimeSelectChanged();
                }
            }
        });
        setChangedListener(wv_day);
        setChangedListener(wv_hours);
        setChangedListener(wv_minutes);
        setChangedListener(wv_seconds);
        if (type.length != 6) {
            throw new RuntimeException("type[] length is not 6");
        }
        wv_year.setVisibility(type[0] ? View.VISIBLE : View.GONE);
        wv_month.setVisibility(type[1] ? View.VISIBLE : View.GONE);
        wv_day.setVisibility(type[2] ? View.VISIBLE : View.GONE);
        wv_hours.setVisibility(type[3] ? View.VISIBLE : View.GONE);
        wv_minutes.setVisibility(type[4] ? View.VISIBLE : View.GONE);
        wv_seconds.setVisibility(type[5] ? View.VISIBLE : View.GONE);
        setContentTextSize();
    }
    /**
     * 设置公历
     *
     * @param year
     * @param month
     * @param day
     * @param h
     * @param m
     * @param s
     */
    private void setSolar(int year, final int month, int day, int h, int m, int s) {
        // 添加大小月月份并将其转换为list,方便之后的判断
        String[] months_big = {"1", "3", "5", "7", "8", "10", "12"};
        String[] months_little = {"4", "6", "9", "11"};
        final List<String> list_big = Arrays.asList(months_big);
        final List<String> list_little = Arrays.asList(months_little);
        currentYear = year;
        // 年
        wv_year = (WheelView) view.findViewById(R.id.year);
        wv_year.setAdapter(new NumericWheelAdapter(startYear, endYear));// 设置"年"的显示数据
        wv_year.setCurrentItem(year - startYear);// 初始化时显示的数据
        wv_year.setGravity(gravity);
        // 月
        wv_month = (WheelView) view.findViewById(R.id.month);
        if (startYear == endYear) {//开始年等于终止年
            wv_month.setAdapter(new NumericWheelAdapter(startMonth, endMonth));
            wv_month.setCurrentItem(month + 1 - startMonth);
        } else if (year == startYear) {
            //起始日期的月份控制
            wv_month.setAdapter(new NumericWheelAdapter(startMonth, 12));
            wv_month.setCurrentItem(month + 1 - startMonth);
        } else if (year == endYear) {
            //终止日期的月份控制
            wv_month.setAdapter(new NumericWheelAdapter(1, endMonth));
            wv_month.setCurrentItem(month);
        } else {
            wv_month.setAdapter(new NumericWheelAdapter(1, 12));
            wv_month.setCurrentItem(month);
        }
        wv_month.setGravity(gravity);
        // 日
        wv_day = (WheelView) view.findViewById(R.id.day);
        boolean leapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
        if (startYear == endYear && startMonth == endMonth) {
            if (list_big.contains(String.valueOf(month + 1))) {
                if (endDay > 31) {
                    endDay = 31;
                }
                wv_day.setAdapter(new NumericWheelAdapter(startDay, endDay));
            } else if (list_little.contains(String.valueOf(month + 1))) {
                if (endDay > 30) {
                    endDay = 30;
                }
                wv_day.setAdapter(new NumericWheelAdapter(startDay, endDay));
            } else {
                // 闰年
                if (leapYear) {
                    if (endDay > 29) {
                        endDay = 29;
                    }
                    wv_day.setAdapter(new NumericWheelAdapter(startDay, endDay));
                } else {
                    if (endDay > 28) {
                        endDay = 28;
                    }
                    wv_day.setAdapter(new NumericWheelAdapter(startDay, endDay));
                }
            }
            wv_day.setCurrentItem(day - startDay);
        } else if (year == startYear && month + 1 == startMonth) {
            // 起始日期的天数控制
            if (list_big.contains(String.valueOf(month + 1))) {
                wv_day.setAdapter(new NumericWheelAdapter(startDay, 31));
            } else if (list_little.contains(String.valueOf(month + 1))) {
                wv_day.setAdapter(new NumericWheelAdapter(startDay, 30));
            } else {
                // 闰年 29,平年 28
                wv_day.setAdapter(new NumericWheelAdapter(startDay, leapYear ? 29 : 28));
            }
            wv_day.setCurrentItem(day - startDay);
        } else if (year == endYear && month + 1 == endMonth) {
            // 终止日期的天数控制
            if (list_big.contains(String.valueOf(month + 1))) {
                if (endDay > 31) {
                    endDay = 31;
                }
                wv_day.setAdapter(new NumericWheelAdapter(1, endDay));
            } else if (list_little.contains(String.valueOf(month + 1))) {
                if (endDay > 30) {
                    endDay = 30;
                }
                wv_day.setAdapter(new NumericWheelAdapter(1, endDay));
            } else {
                // 闰年
                if (leapYear) {
                    if (endDay > 29) {
                        endDay = 29;
                    }
                    wv_day.setAdapter(new NumericWheelAdapter(1, endDay));
                } else {
                    if (endDay > 28) {
                        endDay = 28;
                    }
                    wv_day.setAdapter(new NumericWheelAdapter(1, endDay));
                }
            }
            wv_day.setCurrentItem(day - 1);
        } else {
            // 判断大小月及是否闰年,用来确定"日"的数据
            if (list_big.contains(String.valueOf(month + 1))) {
                wv_day.setAdapter(new NumericWheelAdapter(1, 31));
            } else if (list_little.contains(String.valueOf(month + 1))) {
                wv_day.setAdapter(new NumericWheelAdapter(1, 30));
            } else {
                // 闰年 29,平年 28
                wv_day.setAdapter(new NumericWheelAdapter(startDay, leapYear ? 29 : 28));
            }
            wv_day.setCurrentItem(day - 1);
        }
        wv_day.setGravity(gravity);
        //时
        wv_hours = (WheelView) view.findViewById(R.id.hour);
        wv_hours.setAdapter(new NumericWheelAdapter(0, 23));
        wv_hours.setCurrentItem(h);
        wv_hours.setGravity(gravity);
        //分
        wv_minutes = (WheelView) view.findViewById(R.id.min);
        wv_minutes.setAdapter(new NumericWheelAdapter(0, 59));
        wv_minutes.setCurrentItem(m);
        wv_minutes.setGravity(gravity);
        //秒
        wv_seconds = (WheelView) view.findViewById(R.id.second);
        wv_seconds.setAdapter(new NumericWheelAdapter(0, 59));
        wv_seconds.setCurrentItem(s);
        wv_seconds.setGravity(gravity);
        // 添加"年"监听
        wv_year.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(int index) {
                int year_num = index + startYear;
                currentYear = year_num;
                int currentMonthItem = wv_month.getCurrentItem();//记录上一次的item位置
                // 判断大小月及是否闰年,用来确定"日"的数据
                if (startYear == endYear) {
                    //重新设置月份
                    wv_month.setAdapter(new NumericWheelAdapter(startMonth, endMonth));
                    if (currentMonthItem > wv_month.getAdapter().getItemsCount() - 1) {
                        currentMonthItem = wv_month.getAdapter().getItemsCount() - 1;
                        wv_month.setCurrentItem(currentMonthItem);
                    }
                    int monthNum = currentMonthItem + startMonth;
                    if (startMonth == endMonth) {
                        //重新设置日
                        setReDay(year_num, monthNum, startDay, endDay, list_big, list_little);
                    } else if (monthNum == startMonth) {
                        //重新设置日
                        setReDay(year_num, monthNum, startDay, 31, list_big, list_little);
                    } else if (monthNum == endMonth) {
                        setReDay(year_num, monthNum, 1, endDay, list_big, list_little);
                    } else {//重新设置日
                        setReDay(year_num, monthNum, 1, 31, list_big, list_little);
                    }
                } else if (year_num == startYear) {//等于开始的年
                    //重新设置月份
                    wv_month.setAdapter(new NumericWheelAdapter(startMonth, 12));
                    if (currentMonthItem > wv_month.getAdapter().getItemsCount() - 1) {
                        currentMonthItem = wv_month.getAdapter().getItemsCount() - 1;
                        wv_month.setCurrentItem(currentMonthItem);
                    }
                    int month = currentMonthItem + startMonth;
                    if (month == startMonth) {
                        //重新设置日
                        setReDay(year_num, month, startDay, 31, list_big, list_little);
                    } else {
                        //重新设置日
                        setReDay(year_num, month, 1, 31, list_big, list_little);
                    }
                } else if (year_num == endYear) {
                    //重新设置月份
                    wv_month.setAdapter(new NumericWheelAdapter(1, endMonth));
                    if (currentMonthItem > wv_month.getAdapter().getItemsCount() - 1) {
                        currentMonthItem = wv_month.getAdapter().getItemsCount() - 1;
                        wv_month.setCurrentItem(currentMonthItem);
                    }
                    int monthNum = currentMonthItem + 1;
                    if (monthNum == endMonth) {
                        //重新设置日
                        setReDay(year_num, monthNum, 1, endDay, list_big, list_little);
                    } else {
                        //重新设置日
                        setReDay(year_num, monthNum, 1, 31, list_big, list_little);
                    }
                } else {
                    //重新设置月份
                    wv_month.setAdapter(new NumericWheelAdapter(1, 12));
                    //重新设置日
                    setReDay(year_num, wv_month.getCurrentItem() + 1, 1, 31, list_big, list_little);
                }
                if (mSelectChangeCallback != null) {
                    mSelectChangeCallback.onTimeSelectChanged();
                }
            }
        });
        // 添加"月"监听
        wv_month.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(int index) {
                int month_num = index + 1;
                if (startYear == endYear) {
                    month_num = month_num + startMonth - 1;
                    if (startMonth == endMonth) {
                        //重新设置日
                        setReDay(currentYear, month_num, startDay, endDay, list_big, list_little);
                    } else if (startMonth == month_num) {
                        //重新设置日
                        setReDay(currentYear, month_num, startDay, 31, list_big, list_little);
                    } else if (endMonth == month_num) {
                        setReDay(currentYear, month_num, 1, endDay, list_big, list_little);
                    } else {
                        setReDay(currentYear, month_num, 1, 31, list_big, list_little);
                    }
                } else if (currentYear == startYear) {
                    month_num = month_num + startMonth - 1;
                    if (month_num == startMonth) {
                        //重新设置日
                        setReDay(currentYear, month_num, startDay, 31, list_big, list_little);
                    } else {
                        //重新设置日
                        setReDay(currentYear, month_num, 1, 31, list_big, list_little);
                    }
                } else if (currentYear == endYear) {
                    if (month_num == endMonth) {
                        //重新设置日
                        setReDay(currentYear, wv_month.getCurrentItem() + 1, 1, endDay, list_big, list_little);
                    } else {
                        setReDay(currentYear, wv_month.getCurrentItem() + 1, 1, 31, list_big, list_little);
                    }
                } else {
                    //重新设置日
                    setReDay(currentYear, month_num, 1, 31, list_big, list_little);
                }
                if (mSelectChangeCallback != null) {
                    mSelectChangeCallback.onTimeSelectChanged();
                }
            }
        });
        setChangedListener(wv_day);
        setChangedListener(wv_hours);
        setChangedListener(wv_minutes);
        setChangedListener(wv_seconds);
        if (type.length != 6) {
            throw new IllegalArgumentException("type[] length is not 6");
        }
        wv_year.setVisibility(type[0] ? View.VISIBLE : View.GONE);
        wv_month.setVisibility(type[1] ? View.VISIBLE : View.GONE);
        wv_day.setVisibility(type[2] ? View.VISIBLE : View.GONE);
        wv_hours.setVisibility(type[3] ? View.VISIBLE : View.GONE);
        wv_minutes.setVisibility(type[4] ? View.VISIBLE : View.GONE);
        wv_seconds.setVisibility(type[5] ? View.VISIBLE : View.GONE);
        setContentTextSize();
    }
    private void setChangedListener(WheelView wheelView) {
        if (mSelectChangeCallback != null) {
            wheelView.setOnItemSelectedListener(new OnItemSelectedListener() {
                @Override
                public void onItemSelected(int index) {
                    mSelectChangeCallback.onTimeSelectChanged();
                }
            });
        }
    }
    private void setReDay(int year_num, int monthNum, int startD, int endD, List<String> list_big, List<String> list_little) {
        int currentItem = wv_day.getCurrentItem();
//        int maxItem;
        if (list_big.contains(String.valueOf(monthNum))) {
            if (endD > 31) {
                endD = 31;
            }
            wv_day.setAdapter(new NumericWheelAdapter(startD, endD));
//            maxItem = endD;
        } else if (list_little.contains(String.valueOf(monthNum))) {
            if (endD > 30) {
                endD = 30;
            }
            wv_day.setAdapter(new NumericWheelAdapter(startD, endD));
//            maxItem = endD;
        } else {
            if ((year_num % 4 == 0 && year_num % 100 != 0)
                    || year_num % 400 == 0) {
                if (endD > 29) {
                    endD = 29;
                }
                wv_day.setAdapter(new NumericWheelAdapter(startD, endD));
//                maxItem = endD;
            } else {
                if (endD > 28) {
                    endD = 28;
                }
                wv_day.setAdapter(new NumericWheelAdapter(startD, endD));
//                maxItem = endD;
            }
        }
        if (currentItem > wv_day.getAdapter().getItemsCount() - 1) {
            currentItem = wv_day.getAdapter().getItemsCount() - 1;
            wv_day.setCurrentItem(currentItem);
        }
    }
    private void setContentTextSize() {
        wv_day.setTextSize(textSize);
        wv_month.setTextSize(textSize);
        wv_year.setTextSize(textSize);
        wv_hours.setTextSize(textSize);
        wv_minutes.setTextSize(textSize);
        wv_seconds.setTextSize(textSize);
    }
    public void setLabels(String label_year, String label_month, String label_day, String label_hours, String label_mins, String label_seconds) {
        if (isLunarCalendar) {
            return;
        }
        if (label_year != null) {
            wv_year.setLabel(label_year);
        } else {
            wv_year.setLabel(view.getContext().getString(R.string.pickerview_year));
        }
        if (label_month != null) {
            wv_month.setLabel(label_month);
        } else {
            wv_month.setLabel(view.getContext().getString(R.string.pickerview_month));
        }
        if (label_day != null) {
            wv_day.setLabel(label_day);
        } else {
            wv_day.setLabel(view.getContext().getString(R.string.pickerview_day));
        }
        if (label_hours != null) {
            wv_hours.setLabel(label_hours);
        } else {
            wv_hours.setLabel(view.getContext().getString(R.string.pickerview_hours));
        }
        if (label_mins != null) {
            wv_minutes.setLabel(label_mins);
        } else {
            wv_minutes.setLabel(view.getContext().getString(R.string.pickerview_minutes));
        }
        if (label_seconds != null) {
            wv_seconds.setLabel(label_seconds);
        } else {
            wv_seconds.setLabel(view.getContext().getString(R.string.pickerview_seconds));
        }
    }
    public void setTextXOffset(int x_offset_year, int x_offset_month, int x_offset_day,
                               int x_offset_hours, int x_offset_minutes, int x_offset_seconds) {
        wv_year.setTextXOffset(x_offset_year);
        wv_month.setTextXOffset(x_offset_month);
        wv_day.setTextXOffset(x_offset_day);
        wv_hours.setTextXOffset(x_offset_hours);
        wv_minutes.setTextXOffset(x_offset_minutes);
        wv_seconds.setTextXOffset(x_offset_seconds);
    }
    /**
     * 设置是否循环滚动
     *
     * @param cyclic
     */
    public void setCyclic(boolean cyclic) {
        wv_year.setCyclic(cyclic);
        wv_month.setCyclic(cyclic);
        wv_day.setCyclic(cyclic);
        wv_hours.setCyclic(cyclic);
        wv_minutes.setCyclic(cyclic);
        wv_seconds.setCyclic(cyclic);
    }
    public String getTime() {
        if (isLunarCalendar) {
            //如果是农历 返回对应的公历时间
            return getLunarTime();
        }
        StringBuilder sb = new StringBuilder();
        if (currentYear == startYear) {
           /* int i = wv_month.getCurrentItem() + startMonth;
            System.out.println("i:" + i);*/
            if ((wv_month.getCurrentItem() + startMonth) == startMonth) {
                sb.append((wv_year.getCurrentItem() + startYear)).append("-")
                        .append((wv_month.getCurrentItem() + startMonth)).append("-")
                        .append((wv_day.getCurrentItem() + startDay)).append(" ")
                        .append(wv_hours.getCurrentItem()).append(":")
                        .append(wv_minutes.getCurrentItem()).append(":")
                        .append(wv_seconds.getCurrentItem());
            } else {
                sb.append((wv_year.getCurrentItem() + startYear)).append("-")
                        .append((wv_month.getCurrentItem() + startMonth)).append("-")
                        .append((wv_day.getCurrentItem() + 1)).append(" ")
                        .append(wv_hours.getCurrentItem()).append(":")
                        .append(wv_minutes.getCurrentItem()).append(":")
                        .append(wv_seconds.getCurrentItem());
            }
        } else {
            sb.append((wv_year.getCurrentItem() + startYear)).append("-")
                    .append((wv_month.getCurrentItem() + 1)).append("-")
                    .append((wv_day.getCurrentItem() + 1)).append(" ")
                    .append(wv_hours.getCurrentItem()).append(":")
                    .append(wv_minutes.getCurrentItem()).append(":")
                    .append(wv_seconds.getCurrentItem());
        }
        return sb.toString();
    }
    /**
     * 农历返回对应的公历时间
     *
     * @return
     */
    private String getLunarTime() {
        StringBuilder sb = new StringBuilder();
        int year = wv_year.getCurrentItem() + startYear;
        int month = 1;
        boolean isLeapMonth = false;
        if (ChinaDate.leapMonth(year) == 0) {
            month = wv_month.getCurrentItem() + 1;
        } else {
            if ((wv_month.getCurrentItem() + 1) - ChinaDate.leapMonth(year) <= 0) {
                month = wv_month.getCurrentItem() + 1;
            } else if ((wv_month.getCurrentItem() + 1) - ChinaDate.leapMonth(year) == 1) {
                month = wv_month.getCurrentItem();
                isLeapMonth = true;
            } else {
                month = wv_month.getCurrentItem();
            }
        }
        int day = wv_day.getCurrentItem() + 1;
        int[] solar = LunarCalendar.lunarToSolar(year, month, day, isLeapMonth);
        sb.append(solar[0]).append("-")
                .append(solar[1]).append("-")
                .append(solar[2]).append(" ")
                .append(wv_hours.getCurrentItem()).append(":")
                .append(wv_minutes.getCurrentItem()).append(":")
                .append(wv_seconds.getCurrentItem());
        return sb.toString();
    }
    public View getView() {
        return view;
    }
    public int getStartYear() {
        return startYear;
    }
    public void setStartYear(int startYear) {
        this.startYear = startYear;
    }
    public int getEndYear() {
        return endYear;
    }
    public void setEndYear(int endYear) {
        this.endYear = endYear;
    }
    public void setRangDate(Calendar startDate, Calendar endDate) {
        if (startDate == null && endDate != null) {
            int year = endDate.get(Calendar.YEAR);
            int month = endDate.get(Calendar.MONTH) + 1;
            int day = endDate.get(Calendar.DAY_OF_MONTH);
            if (year > startYear) {
                this.endYear = year;
                this.endMonth = month;
                this.endDay = day;
            } else if (year == startYear) {
                if (month > startMonth) {
                    this.endYear = year;
                    this.endMonth = month;
                    this.endDay = day;
                } else if (month == startMonth) {
                    if (day > startDay) {
                        this.endYear = year;
                        this.endMonth = month;
                        this.endDay = day;
                    }
                }
            }
        } else if (startDate != null && endDate == null) {
            int year = startDate.get(Calendar.YEAR);
            int month = startDate.get(Calendar.MONTH) + 1;
            int day = startDate.get(Calendar.DAY_OF_MONTH);
            if (year < endYear) {
                this.startMonth = month;
                this.startDay = day;
                this.startYear = year;
            } else if (year == endYear) {
                if (month < endMonth) {
                    this.startMonth = month;
                    this.startDay = day;
                    this.startYear = year;
                } else if (month == endMonth) {
                    if (day < endDay) {
                        this.startMonth = month;
                        this.startDay = day;
                        this.startYear = year;
                    }
                }
            }
        } else if (startDate != null && endDate != null) {
            this.startYear = startDate.get(Calendar.YEAR);
            this.endYear = endDate.get(Calendar.YEAR);
            this.startMonth = startDate.get(Calendar.MONTH) + 1;
            this.endMonth = endDate.get(Calendar.MONTH) + 1;
            this.startDay = startDate.get(Calendar.DAY_OF_MONTH);
            this.endDay = endDate.get(Calendar.DAY_OF_MONTH);
        }
    }
    /**
     * 设置间距倍数,但是只能在1.0-4.0f之间
     *
     * @param lineSpacingMultiplier
     */
    public void setLineSpacingMultiplier(float lineSpacingMultiplier) {
        wv_day.setLineSpacingMultiplier(lineSpacingMultiplier);
        wv_month.setLineSpacingMultiplier(lineSpacingMultiplier);
        wv_year.setLineSpacingMultiplier(lineSpacingMultiplier);
        wv_hours.setLineSpacingMultiplier(lineSpacingMultiplier);
        wv_minutes.setLineSpacingMultiplier(lineSpacingMultiplier);
        wv_seconds.setLineSpacingMultiplier(lineSpacingMultiplier);
    }
    /**
     * 设置分割线的颜色
     *
     * @param dividerColor
     */
    public void setDividerColor(int dividerColor) {
        wv_day.setDividerColor(dividerColor);
        wv_month.setDividerColor(dividerColor);
        wv_year.setDividerColor(dividerColor);
        wv_hours.setDividerColor(dividerColor);
        wv_minutes.setDividerColor(dividerColor);
        wv_seconds.setDividerColor(dividerColor);
    }
    /**
     * 设置分割线的类型
     *
     * @param dividerType
     */
    public void setDividerType(WheelView.DividerType dividerType) {
        wv_day.setDividerType(dividerType);
        wv_month.setDividerType(dividerType);
        wv_year.setDividerType(dividerType);
        wv_hours.setDividerType(dividerType);
        wv_minutes.setDividerType(dividerType);
        wv_seconds.setDividerType(dividerType);
    }
    /**
     * 设置分割线之间的文字的颜色
     *
     * @param textColorCenter
     */
    public void setTextColorCenter(int textColorCenter) {
        wv_day.setTextColorCenter(textColorCenter);
        wv_month.setTextColorCenter(textColorCenter);
        wv_year.setTextColorCenter(textColorCenter);
        wv_hours.setTextColorCenter(textColorCenter);
        wv_minutes.setTextColorCenter(textColorCenter);
        wv_seconds.setTextColorCenter(textColorCenter);
    }
    /**
     * 设置分割线以外文字的颜色
     *
     * @param textColorOut
     */
    public void setTextColorOut(int textColorOut) {
        wv_day.setTextColorOut(textColorOut);
        wv_month.setTextColorOut(textColorOut);
        wv_year.setTextColorOut(textColorOut);
        wv_hours.setTextColorOut(textColorOut);
        wv_minutes.setTextColorOut(textColorOut);
        wv_seconds.setTextColorOut(textColorOut);
    }
    /**
     * @param isCenterLabel 是否只显示中间选中项的
     */
    public void isCenterLabel(boolean isCenterLabel) {
        wv_day.isCenterLabel(isCenterLabel);
        wv_month.isCenterLabel(isCenterLabel);
        wv_year.isCenterLabel(isCenterLabel);
        wv_hours.isCenterLabel(isCenterLabel);
        wv_minutes.isCenterLabel(isCenterLabel);
        wv_seconds.isCenterLabel(isCenterLabel);
    }
    public void setSelectChangeCallback(ISelectTimeCallback mSelectChangeCallback) {
        this.mSelectChangeCallback = mSelectChangeCallback;
    }
    public void setItemsVisible(int itemsVisibleCount) {
        wv_day.setItemsVisibleCount(itemsVisibleCount);
        wv_month.setItemsVisibleCount(itemsVisibleCount);
        wv_year.setItemsVisibleCount(itemsVisibleCount);
        wv_hours.setItemsVisibleCount(itemsVisibleCount);
        wv_minutes.setItemsVisibleCount(itemsVisibleCount);
        wv_seconds.setItemsVisibleCount(itemsVisibleCount);
    }
    public void setAlphaGradient(boolean isAlphaGradient) {
        wv_day.setAlphaGradient(isAlphaGradient);
        wv_month.setAlphaGradient(isAlphaGradient);
        wv_year.setAlphaGradient(isAlphaGradient);
        wv_hours.setAlphaGradient(isAlphaGradient);
        wv_minutes.setAlphaGradient(isAlphaGradient);
        wv_seconds.setAlphaGradient(isAlphaGradient);
    }
}
pickerview/src/main/res/anim/pickerview_dialog_scale_in.xml
New file
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
  <alpha
      android:duration="@integer/animation_default_duration"
      android:fromAlpha="0.0"
      android:toAlpha="1.0" />
  <scale
      android:duration="@integer/animation_default_duration"
      android:fromXScale="0.0"
      android:toXScale="1.0"
      android:fromYScale="0.0"
      android:toYScale="1.0"
      android:pivotX="50%"
      android:pivotY="50%"/>
</set>
pickerview/src/main/res/anim/pickerview_dialog_scale_out.xml
New file
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
  <alpha
      android:duration="@integer/animation_default_duration"
      android:fromAlpha="1.0"
      android:toAlpha="0.3" />
  <scale
      android:duration="@integer/animation_default_duration"
      android:fromXScale="1.0"
      android:toXScale="0.0"
      android:fromYScale="1.0"
      android:toYScale="0.0"
      android:pivotX="50%"
      android:pivotY="50%"/>
</set>
pickerview/src/main/res/anim/pickerview_slide_in_bottom.xml
New file
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="false">
  <translate
      android:duration="@integer/animation_default_duration"
      android:fromXDelta="0%"
      android:toXDelta="0%"
      android:fromYDelta="100%"
      android:toYDelta="0%"/>
</set>
pickerview/src/main/res/anim/pickerview_slide_out_bottom.xml
New file
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="false">
  <translate
      android:duration="@integer/animation_default_duration"
      android:fromXDelta="0%"
      android:toXDelta="0%"
      android:fromYDelta="0%"
      android:toYDelta="100%"/>
</set>
pickerview/src/main/res/drawable/selector_pickerview_btn.xml
New file
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:color="@color/pickerview_timebtn_pre" />
    <item android:color="@color/pickerview_timebtn_nor" />
</selector>
pickerview/src/main/res/layout/include_pickerview_topbar.xml
New file
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rv_topbar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/pickerview_bgColor_default">
    <Button
        android:id="@+id/btnCancel"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:background="@android:color/transparent"
        android:gravity="left|center_vertical"
        android:paddingLeft="@dimen/pickerview_topbar_padding"
        android:text="@string/pickerview_cancel"
        android:textAllCaps="false"
        android:textColor="@drawable/selector_pickerview_btn"
        android:textSize="@dimen/pickerview_topbar_btn_textsize" />
    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_toLeftOf="@+id/btnSubmit"
        android:layout_toRightOf="@+id/btnCancel"
        android:gravity="center"
        android:textColor="@color/pickerview_topbar_title"
        android:textSize="@dimen/pickerview_topbar_title_textsize"
        />
    <Button
        android:id="@id/btnSubmit"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:background="@android:color/transparent"
        android:gravity="right|center_vertical"
        android:paddingRight="@dimen/pickerview_topbar_padding"
        android:text="@string/pickerview_submit"
        android:textAllCaps="false"
        android:textColor="@drawable/selector_pickerview_btn"
        android:textSize="@dimen/pickerview_topbar_btn_textsize" />
</RelativeLayout>
pickerview/src/main/res/layout/layout_basepickerview.xml
New file
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    android:id="@+id/outmost_container"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="true"
    android:background="@color/pickerview_bgColor_overlay">
    <FrameLayout
        android:id="@+id/content_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </FrameLayout>
</FrameLayout>
pickerview/src/main/res/layout/pickerview_options.xml
New file
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <include
        layout="@layout/include_pickerview_topbar"
        android:layout_width="match_parent"
        android:layout_height="50dp" />
    <View
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="#E1E1E1" />
    <LinearLayout
        android:id="@+id/optionspicker"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:gravity="center"
        android:minHeight="180dp"
        android:orientation="horizontal">
        <com.contrarywind.view.WheelView
            android:id="@+id/options1"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />
        <com.contrarywind.view.WheelView
            android:id="@+id/options2"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />
        <com.contrarywind.view.WheelView
            android:id="@+id/options3"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />
    </LinearLayout>
</LinearLayout>
pickerview/src/main/res/layout/pickerview_time.xml
New file
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <include
        layout="@layout/include_pickerview_topbar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/pickerview_topbar_height" />
    <View
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="#E1E1E1" />
    <LinearLayout
        android:id="@+id/timepicker"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:gravity="center"
        android:minHeight="150dp"
        android:orientation="horizontal">
        <com.contrarywind.view.WheelView
            android:id="@+id/year"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="0.96" />
        <com.contrarywind.view.WheelView
            android:id="@+id/month"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />
        <com.contrarywind.view.WheelView
            android:id="@+id/day"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />
        <com.contrarywind.view.WheelView
            android:id="@+id/hour"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />
        <com.contrarywind.view.WheelView
            android:id="@+id/min"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />
        <com.contrarywind.view.WheelView
            android:id="@+id/second"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />
    </LinearLayout>
</LinearLayout>
pickerview/src/main/res/values-en/strings.xml
New file
@@ -0,0 +1,11 @@
<resources>
    <string name="pickerview_cancel">Cancel</string>
    <string name="pickerview_submit">Confirm</string>
    <string name="pickerview_year"> </string>
    <string name="pickerview_month"> </string>
    <string name="pickerview_day"> </string>
    <string name="pickerview_hours"> </string>
    <string name="pickerview_minutes"> </string>
    <string name="pickerview_seconds"> </string>
</resources>
pickerview/src/main/res/values/dimens.xml
New file
@@ -0,0 +1,13 @@
<resources>
    <!-- 顶部按钮栏高度 -->
    <dimen name="pickerview_topbar_height">52dp</dimen>
    <!-- 顶部按钮padding -->
    <dimen name="pickerview_topbar_padding">20dp</dimen>
    <!-- 顶部按钮文字大小 -->
    <dimen name="pickerview_topbar_btn_textsize">17sp</dimen>
    <dimen name="pickerview_topbar_title_textsize">18sp</dimen>
    <!-- 选项文字大小 -->
    <dimen name="pickerview_textsize">20sp</dimen>
</resources>
pickerview/src/main/res/values/integers.xml
New file
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <integer name="animation_default_duration">300</integer>
</resources>
pickerview/src/main/res/values/strings.xml
New file
@@ -0,0 +1,11 @@
<resources>
    <string name="pickerview_cancel">取消</string>
    <string name="pickerview_submit">确定</string>
    <string name="pickerview_year">年</string>
    <string name="pickerview_month">月</string>
    <string name="pickerview_day">日</string>
    <string name="pickerview_hours">时</string>
    <string name="pickerview_minutes">分</string>
    <string name="pickerview_seconds">秒</string>
</resources>
pickerview/src/main/res/values/styles.xml
New file
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="custom_dialog2" parent="@android:style/Theme.Dialog">
        <item name="android:windowFrame">@null</item>
        <!-- Dialog的windowFrame框为无 -->
        <item name="android:windowIsFloating">true</item>
        <!-- 是否漂现在activity上 -->
        <item name="android:windowIsTranslucent">true</item>
        <!-- 是否半透明 -->
        <item name="android:windowNoTitle">true</item>
        <item name="android:background">@null</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowContentOverlay">@null</item>
        <!-- 去除黑色边框的关键设置项 -->
        <item name="android:backgroundDimEnabled">true</item>
        <!-- 屏幕背景是否变暗 -->
        <item name="android:backgroundDimAmount">0.3</item>
    </style>
    <style name="picker_view_scale_anim" mce_bogus="1" parent="android:Animation">
        <item name="android:windowEnterAnimation">@anim/pickerview_dialog_scale_in</item>
        <item name="android:windowExitAnimation">@anim/pickerview_dialog_scale_out</item>
    </style>
    <style name="picker_view_slide_anim" mce_bogus="1" parent="android:Animation">
        <item name="android:windowEnterAnimation">@anim/pickerview_slide_in_bottom</item>
        <item name="android:windowExitAnimation">@anim/pickerview_slide_out_bottom</item>
    </style>
</resources>
settings.gradle
@@ -3,3 +3,5 @@
include ':third-zxing'
include ':countrypicker'
include ':aa_chart_core'
include ':pickerview'
include ':wheelview'
third-zxing/build.gradle
@@ -2,12 +2,12 @@
android {
    compileSdkVersion 29
    compileSdkVersion 30
    buildToolsVersion "29.0.1"
    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 26
        minSdkVersion 23
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
wheelview/.gitignore
New file
@@ -0,0 +1,12 @@
/build
*.idea
/.idea/libraries
/.idea/modules
/.idea/compiler.xml
/.idea/gradle.xml
/.idea/jarRepositories.xml
/.idea/misc.xml
/.idea/modules.xml
/.idea/vcs.xml
/.idea/workspace.xml
/release
wheelview/build.gradle
New file
@@ -0,0 +1,47 @@
apply plugin: 'com.android.library'
//apply plugin: 'com.novoda.bintray-release'//添加插件
android {
    compileSdkVersion 31
    defaultConfig {
        minSdkVersion 23
        targetSdkVersion 31
        versionCode 31
        versionName "4.1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    lintOptions {
        abortOnError false
    }
}
//allprojects {
//    tasks.withType(Javadoc) {//兼容中文字符
//        options{
//            encoding "UTF-8"
//            charSet 'UTF-8'
//            links "http://docs.oracle.com/javase/7/docs/api"
//        }
//    }
//}
//publish {
//    userOrg = 'contrarywind'//bintray.com 用户名/组织名 user/org name
//    groupId = 'com.contrarywind'//JCenter上显示的路径 path
//    artifactId = 'wheelview'//项目名称 project name
//    publishVersion = '4.1.0'//版本号 version code
//    desc = 'this is a wheelview for android'//项目描述 description
//    website = 'https://github.com/Bigkoo/Android-PickerView' //项目网址链接 link
//}
dependencies {
//    compile fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:support-annotations:28.0.0'
}
wheelview/proguard-rules.pro
New file
@@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Users\song\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
wheelview/src/androidTest/java/test/wheelview/ExampleInstrumentedTest.java
New file
@@ -0,0 +1,26 @@
package test.wheelview;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
 * Instrumentation test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() throws Exception {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();
        assertEquals("test.wheelview.test", appContext.getPackageName());
    }
}
wheelview/src/main/AndroidManifest.xml
New file
@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.contrarywind.view">
</manifest>
wheelview/src/main/java/com/contrarywind/adapter/WheelAdapter.java
New file
@@ -0,0 +1,25 @@
package com.contrarywind.adapter;
public interface WheelAdapter<T> {
    /**
     * Gets items count
     * @return the count of wheel items
     */
    int getItemsCount();
    /**
     * Gets a wheel item by index.
     * @param index the item index
     * @return the wheel item text or null
     */
    T getItem(int index);
    /**
     * Gets maximum item length. It is used to determine the wheel width.
     * If -1 is returned there will be used the default wheel width.
     * @param o  the item object
     * @return the maximum item length or -1
     */
    int indexOf(T o);
}
wheelview/src/main/java/com/contrarywind/interfaces/IPickerViewData.java
New file
@@ -0,0 +1,8 @@
package com.contrarywind.interfaces;
/**
 * Created by Sai on 2016/7/13.
 */
public interface IPickerViewData {
    String getPickerViewText();
}
wheelview/src/main/java/com/contrarywind/listener/LoopViewGestureListener.java
New file
@@ -0,0 +1,25 @@
package com.contrarywind.listener;
import android.view.MotionEvent;
import com.contrarywind.view.WheelView;
/**
 * 手势监听
 */
public final class LoopViewGestureListener extends android.view.GestureDetector.SimpleOnGestureListener {
    private final WheelView wheelView;
    public LoopViewGestureListener(WheelView wheelView) {
        this.wheelView = wheelView;
    }
    @Override
    public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        wheelView.scrollBy(velocityY);
        return true;
    }
}
wheelview/src/main/java/com/contrarywind/listener/OnItemSelectedListener.java
New file
@@ -0,0 +1,6 @@
package com.contrarywind.listener;
public interface OnItemSelectedListener {
    void onItemSelected(int index);
}
wheelview/src/main/java/com/contrarywind/timer/InertiaTimerTask.java
New file
@@ -0,0 +1,79 @@
package com.contrarywind.timer;
import com.contrarywind.view.WheelView;
import java.util.TimerTask;
/**
 * 滚动惯性的实现
 *
 * @author 小嵩
 * date:  2017-12-23 23:20:44
 */
public final class InertiaTimerTask extends TimerTask {
    private float mCurrentVelocityY; //当前滑动速度
    private final float mFirstVelocityY;//手指离开屏幕时的初始速度
    private final WheelView mWheelView;
    /**
     * @param wheelView 滚轮对象
     * @param velocityY Y轴滑行速度
     */
    public InertiaTimerTask(WheelView wheelView, float velocityY) {
        super();
        this.mWheelView = wheelView;
        this.mFirstVelocityY = velocityY;
        mCurrentVelocityY = Integer.MAX_VALUE;
    }
    @Override
    public final void run() {
        //防止闪动,对速度做一个限制。
        if (mCurrentVelocityY == Integer.MAX_VALUE) {
            if (Math.abs(mFirstVelocityY) > 2000F) {
                mCurrentVelocityY = mFirstVelocityY > 0 ? 2000F : -2000F;
            } else {
                mCurrentVelocityY = mFirstVelocityY;
            }
        }
        //发送handler消息 处理平顺停止滚动逻辑
        if (Math.abs(mCurrentVelocityY) >= 0.0F && Math.abs(mCurrentVelocityY) <= 20F) {
            mWheelView.cancelFuture();
            mWheelView.getHandler().sendEmptyMessage(MessageHandler.WHAT_SMOOTH_SCROLL);
            return;
        }
        int dy = (int) (mCurrentVelocityY / 100F);
        mWheelView.setTotalScrollY(mWheelView.getTotalScrollY() - dy);
        if (!mWheelView.isLoop()) {
            float itemHeight = mWheelView.getItemHeight();
            float top = (-mWheelView.getInitPosition()) * itemHeight;
            float bottom = (mWheelView.getItemsCount() - 1 - mWheelView.getInitPosition()) * itemHeight;
            if (mWheelView.getTotalScrollY() - itemHeight * 0.25 < top) {
                top = mWheelView.getTotalScrollY() + dy;
            } else if (mWheelView.getTotalScrollY() + itemHeight * 0.25 > bottom) {
                bottom = mWheelView.getTotalScrollY() + dy;
            }
            if (mWheelView.getTotalScrollY() <= top) {
                mCurrentVelocityY = 40F;
                mWheelView.setTotalScrollY((int) top);
            } else if (mWheelView.getTotalScrollY() >= bottom) {
                mWheelView.setTotalScrollY((int) bottom);
                mCurrentVelocityY = -40F;
            }
        }
        if (mCurrentVelocityY < 0.0F) {
            mCurrentVelocityY = mCurrentVelocityY + 20F;
        } else {
            mCurrentVelocityY = mCurrentVelocityY - 20F;
        }
        //刷新UI
        mWheelView.getHandler().sendEmptyMessage(MessageHandler.WHAT_INVALIDATE_LOOP_VIEW);
    }
}
wheelview/src/main/java/com/contrarywind/timer/MessageHandler.java
New file
@@ -0,0 +1,42 @@
package com.contrarywind.timer;
import android.os.Handler;
import android.os.Message;
import com.contrarywind.view.WheelView;
/**
 * Handler 消息类
 *
 * @author 小嵩
 * date: 2017-12-23 23:20:44
 */
public final class MessageHandler extends Handler {
    public static final int WHAT_INVALIDATE_LOOP_VIEW = 1000;
    public static final int WHAT_SMOOTH_SCROLL = 2000;
    public static final int WHAT_ITEM_SELECTED = 3000;
    private final WheelView wheelView;
    public MessageHandler(WheelView wheelView) {
        this.wheelView = wheelView;
    }
    @Override
    public final void handleMessage(Message msg) {
        switch (msg.what) {
            case WHAT_INVALIDATE_LOOP_VIEW:
                wheelView.invalidate();
                break;
            case WHAT_SMOOTH_SCROLL:
                wheelView.smoothScroll(WheelView.ACTION.FLING);
                break;
            case WHAT_ITEM_SELECTED:
                wheelView.onItemSelected();
                break;
        }
    }
}
wheelview/src/main/java/com/contrarywind/timer/SmoothScrollTimerTask.java
New file
@@ -0,0 +1,64 @@
package com.contrarywind.timer;
import com.contrarywind.view.WheelView;
import java.util.TimerTask;
/**
 * 平滑滚动的实现
 *
 * @author 小嵩
 */
public final class SmoothScrollTimerTask extends TimerTask {
    private int realTotalOffset;
    private int realOffset;
    private int offset;
    private final WheelView wheelView;
    public SmoothScrollTimerTask(WheelView wheelView, int offset) {
        this.wheelView = wheelView;
        this.offset = offset;
        realTotalOffset = Integer.MAX_VALUE;
        realOffset = 0;
    }
    @Override
    public final void run() {
        if (realTotalOffset == Integer.MAX_VALUE) {
            realTotalOffset = offset;
        }
        //把要滚动的范围细分成10小份,按10小份单位来重绘
        realOffset = (int) ((float) realTotalOffset * 0.1F);
        if (realOffset == 0) {
            if (realTotalOffset < 0) {
                realOffset = -1;
            } else {
                realOffset = 1;
            }
        }
        if (Math.abs(realTotalOffset) <= 1) {
            wheelView.cancelFuture();
            wheelView.getHandler().sendEmptyMessage(MessageHandler.WHAT_ITEM_SELECTED);
        } else {
            wheelView.setTotalScrollY(wheelView.getTotalScrollY() + realOffset);
            //这里如果不是循环模式,则点击空白位置需要回滚,不然就会出现选到-1 item的 情况
            if (!wheelView.isLoop()) {
                float itemHeight = wheelView.getItemHeight();
                float top = (float) (-wheelView.getInitPosition()) * itemHeight;
                float bottom = (float) (wheelView.getItemsCount() - 1 - wheelView.getInitPosition()) * itemHeight;
                if (wheelView.getTotalScrollY() <= top || wheelView.getTotalScrollY() >= bottom) {
                    wheelView.setTotalScrollY(wheelView.getTotalScrollY() - realOffset);
                    wheelView.cancelFuture();
                    wheelView.getHandler().sendEmptyMessage(MessageHandler.WHAT_ITEM_SELECTED);
                    return;
                }
            }
            wheelView.getHandler().sendEmptyMessage(MessageHandler.WHAT_INVALIDATE_LOOP_VIEW);
            realTotalOffset = realTotalOffset - realOffset;
        }
    }
}
wheelview/src/main/java/com/contrarywind/view/WheelView.java
New file
@@ -0,0 +1,840 @@
package com.contrarywind.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Handler;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import com.contrarywind.adapter.WheelAdapter;
import com.contrarywind.interfaces.IPickerViewData;
import com.contrarywind.listener.LoopViewGestureListener;
import com.contrarywind.listener.OnItemSelectedListener;
import com.contrarywind.timer.InertiaTimerTask;
import com.contrarywind.timer.MessageHandler;
import com.contrarywind.timer.SmoothScrollTimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
 * 3d滚轮控件
 */
public class WheelView extends View {
    public enum ACTION { // 点击,滑翔(滑到尽头),拖拽事件
        CLICK, FLING, DAGGLE
    }
    public enum DividerType { // 分隔线类型
        FILL, WRAP, CIRCLE
    }
    private static final String[] TIME_NUM = {"00", "01", "02", "03", "04", "05", "06", "07", "08", "09"};
    private DividerType dividerType;//分隔线类型
    private Context context;
    private Handler handler;
    private GestureDetector gestureDetector;
    private OnItemSelectedListener onItemSelectedListener;
    private boolean isOptions = false;
    private boolean isCenterLabel = true;
    // Timer mTimer;
    private ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor();
    private ScheduledFuture<?> mFuture;
    private Paint paintOuterText;
    private Paint paintCenterText;
    private Paint paintIndicator;
    private WheelAdapter adapter;
    private String label;//附加单位
    private int textSize;//选项的文字大小
    private int maxTextWidth;
    private int maxTextHeight;
    private int textXOffset;
    private float itemHeight;//每行高度
    private Typeface typeface = Typeface.MONOSPACE;//字体样式,默认是等宽字体
    private int textColorOut;
    private int textColorCenter;
    private int dividerColor;
    private int dividerWidth;
    // 条目间距倍数
    private float lineSpacingMultiplier = 1.6F;
    private boolean isLoop;
    // 第一条线Y坐标值
    private float firstLineY;
    //第二条线Y坐标
    private float secondLineY;
    //中间label绘制的Y坐标
    private float centerY;
    //当前滚动总高度y值
    private float totalScrollY;
    //初始化默认选中项
    private int initPosition;
    //选中的Item是第几个
    private int selectedItem;
    private int preCurrentIndex;
    // 绘制几个条目,实际上第一项和最后一项Y轴压缩成0%了,所以可见的数目实际为9
    private int itemsVisible = 11;
    private int measuredHeight;// WheelView 控件高度
    private int measuredWidth;// WheelView 控件宽度
    // 半径
    private int radius;
    private int mOffset = 0;
    private float previousY = 0;
    private long startTime = 0;
    // 修改这个值可以改变滑行速度
    private static final int VELOCITY_FLING = 5;
    private int widthMeasureSpec;
    private int mGravity = Gravity.CENTER;
    private int drawCenterContentStart = 0;//中间选中文字开始绘制位置
    private int drawOutContentStart = 0;//非中间文字开始绘制位置
    private static final float SCALE_CONTENT = 0.8F;//非中间文字则用此控制高度,压扁形成3d错觉
    private float CENTER_CONTENT_OFFSET;//偏移量
    private boolean isAlphaGradient = false; //透明度渐变
    public WheelView(Context context) {
        this(context, null);
    }
    public WheelView(Context context, AttributeSet attrs) {
        super(context, attrs);
        textSize = getResources().getDimensionPixelSize(R.dimen.pickerview_textsize);//默认大小
        DisplayMetrics dm = getResources().getDisplayMetrics();
        float density = dm.density; // 屏幕密度比(0.75/1.0/1.5/2.0/3.0)
        if (density < 1) {//根据密度不同进行适配
            CENTER_CONTENT_OFFSET = 2.4F;
        } else if (1 <= density && density < 2) {
            CENTER_CONTENT_OFFSET = 4.0F;
        } else if (2 <= density && density < 3) {
            CENTER_CONTENT_OFFSET = 6.0F;
        } else if (density >= 3) {
            CENTER_CONTENT_OFFSET = density * 2.5F;
        }
        if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.pickerview, 0, 0);
            mGravity = a.getInt(R.styleable.pickerview_wheelview_gravity, Gravity.CENTER);
            textColorOut = a.getColor(R.styleable.pickerview_wheelview_textColorOut, 0xFFa8a8a8);
            textColorCenter = a.getColor(R.styleable.pickerview_wheelview_textColorCenter, 0xFF2a2a2a);
            dividerColor = a.getColor(R.styleable.pickerview_wheelview_dividerColor, 0xFFd5d5d5);
            dividerWidth = a.getDimensionPixelSize(R.styleable.pickerview_wheelview_dividerWidth, 2);
            textSize = a.getDimensionPixelOffset(R.styleable.pickerview_wheelview_textSize, textSize);
            lineSpacingMultiplier = a.getFloat(R.styleable.pickerview_wheelview_lineSpacingMultiplier, lineSpacingMultiplier);
            a.recycle();//回收内存
        }
        judgeLineSpace();
        initLoopView(context);
    }
    /**
     * 判断间距是否在1.0-4.0之间
     */
    private void judgeLineSpace() {
        if (lineSpacingMultiplier < 1.0f) {
            lineSpacingMultiplier = 1.0f;
        } else if (lineSpacingMultiplier > 4.0f) {
            lineSpacingMultiplier = 4.0f;
        }
    }
    private void initLoopView(Context context) {
        this.context = context;
        handler = new MessageHandler(this);
        gestureDetector = new GestureDetector(context, new LoopViewGestureListener(this));
        gestureDetector.setIsLongpressEnabled(false);
        isLoop = true;
        totalScrollY = 0;
        initPosition = -1;
        initPaints();
    }
    private void initPaints() {
        paintOuterText = new Paint();
        paintOuterText.setColor(textColorOut);
        paintOuterText.setAntiAlias(true);
        paintOuterText.setTypeface(typeface);
        paintOuterText.setTextSize(textSize);
        paintCenterText = new Paint();
        paintCenterText.setColor(textColorCenter);
        paintCenterText.setAntiAlias(true);
        paintCenterText.setTextScaleX(1.1F);
        paintCenterText.setTypeface(typeface);
        paintCenterText.setTextSize(textSize);
        paintIndicator = new Paint();
        paintIndicator.setColor(dividerColor);
        paintIndicator.setAntiAlias(true);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }
    private void reMeasure() {//重新测量
        if (adapter == null) {
            return;
        }
        measureTextWidthHeight();
        //半圆的周长 = item高度乘以item数目-1
        int halfCircumference = (int) (itemHeight * (itemsVisible - 1));
        //整个圆的周长除以PI得到直径,这个直径用作控件的总高度
        measuredHeight = (int) ((halfCircumference * 2) / Math.PI);
        //求出半径
        radius = (int) (halfCircumference / Math.PI);
        //控件宽度,这里支持weight
        measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
        //计算两条横线 和 选中项画笔的基线Y位置
        firstLineY = (measuredHeight - itemHeight) / 2.0F;
        secondLineY = (measuredHeight + itemHeight) / 2.0F;
        centerY = secondLineY - (itemHeight - maxTextHeight) / 2.0f - CENTER_CONTENT_OFFSET;
        //初始化显示的item的position
        if (initPosition == -1) {
            if (isLoop) {
                initPosition = (adapter.getItemsCount() + 1) / 2;
            } else {
                initPosition = 0;
            }
        }
        preCurrentIndex = initPosition;
    }
    /**
     * 计算最大length的Text的宽高度
     */
    private void measureTextWidthHeight() {
        Rect rect = new Rect();
        for (int i = 0; i < adapter.getItemsCount(); i++) {
            String s1 = getContentText(adapter.getItem(i));
            paintCenterText.getTextBounds(s1, 0, s1.length(), rect);
            int textWidth = rect.width();
            if (textWidth > maxTextWidth) {
                maxTextWidth = textWidth;
            }
        }
        paintCenterText.getTextBounds("\u661F\u671F", 0, 2, rect); // 星期的字符编码(以它为标准高度)
        maxTextHeight = rect.height() + 2;
        itemHeight = lineSpacingMultiplier * maxTextHeight;
    }
    public void smoothScroll(ACTION action) {//平滑滚动的实现
        cancelFuture();
        if (action == ACTION.FLING || action == ACTION.DAGGLE) {
            mOffset = (int) ((totalScrollY % itemHeight + itemHeight) % itemHeight);
            if ((float) mOffset > itemHeight / 2.0F) {//如果超过Item高度的一半,滚动到下一个Item去
                mOffset = (int) (itemHeight - (float) mOffset);
            } else {
                mOffset = -mOffset;
            }
        }
        //停止的时候,位置有偏移,不是全部都能正确停止到中间位置的,这里把文字位置挪回中间去
        mFuture = mExecutor.scheduleWithFixedDelay(new SmoothScrollTimerTask(this, mOffset), 0, 10, TimeUnit.MILLISECONDS);
    }
    public final void scrollBy(float velocityY) {//滚动惯性的实现
        cancelFuture();
        mFuture = mExecutor.scheduleWithFixedDelay(new InertiaTimerTask(this, velocityY), 0, VELOCITY_FLING, TimeUnit.MILLISECONDS);
    }
    public void cancelFuture() {
        if (mFuture != null && !mFuture.isCancelled()) {
            mFuture.cancel(true);
            mFuture = null;
        }
    }
    /**
     * 设置是否循环滚动
     *
     * @param cyclic 是否循环
     */
    public final void setCyclic(boolean cyclic) {
        isLoop = cyclic;
    }
    public final void setTypeface(Typeface font) {
        typeface = font;
        paintOuterText.setTypeface(typeface);
        paintCenterText.setTypeface(typeface);
    }
    public final void setTextSize(float size) {
        if (size > 0.0F) {
            textSize = (int) (context.getResources().getDisplayMetrics().density * size);
            paintOuterText.setTextSize(textSize);
            paintCenterText.setTextSize(textSize);
        }
    }
    public final void setCurrentItem(int currentItem) {
        //不添加这句,当这个wheelView不可见时,默认都是0,会导致获取到的时间错误
        this.selectedItem = currentItem;
        this.initPosition = currentItem;
        totalScrollY = 0;//回归顶部,不然重设setCurrentItem的话位置会偏移的,就会显示出不对位置的数据
        invalidate();
    }
    public final void setOnItemSelectedListener(OnItemSelectedListener OnItemSelectedListener) {
        this.onItemSelectedListener = OnItemSelectedListener;
    }
    public final void setAdapter(WheelAdapter adapter) {
        this.adapter = adapter;
        reMeasure();
        invalidate();
    }
    public void setItemsVisibleCount(int visibleCount) {
        if (visibleCount % 2 == 0) {
            visibleCount += 1;
        }
        this.itemsVisible = visibleCount + 2; //第一条和最后一条
    }
    public void setAlphaGradient(boolean alphaGradient) {
        isAlphaGradient = alphaGradient;
    }
    public final WheelAdapter getAdapter() {
        return adapter;
    }
    public final int getCurrentItem() {
        // return selectedItem;
        if (adapter == null) {
            return 0;
        }
        if (isLoop && (selectedItem < 0 || selectedItem >= adapter.getItemsCount())) {
            return Math.max(0, Math.min(Math.abs(Math.abs(selectedItem) - adapter.getItemsCount()), adapter.getItemsCount() - 1));
        }
        return Math.max(0, Math.min(selectedItem, adapter.getItemsCount() - 1));
    }
    public final void onItemSelected() {
        if (onItemSelectedListener != null) {
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    onItemSelectedListener.onItemSelected(getCurrentItem());
                }
            }, 200L);
        }
    }
    @Override
    protected void onDraw(Canvas canvas) {
        if (adapter == null) {
            return;
        }
        //initPosition越界会造成preCurrentIndex的值不正确
        initPosition = Math.min(Math.max(0, initPosition), adapter.getItemsCount() - 1);
        //滚动的Y值高度除去每行Item的高度,得到滚动了多少个item,即change数
        //滚动偏移值,用于记录滚动了多少个item
        int change = (int) (totalScrollY / itemHeight);
        // Log.d("change", "" + change);
        try {
            //滚动中实际的预选中的item(即经过了中间位置的item) = 滑动前的位置 + 滑动相对位置
            preCurrentIndex = initPosition + change % adapter.getItemsCount();
        } catch (ArithmeticException e) {
            Log.e("WheelView", "出错了!adapter.getItemsCount() == 0,联动数据不匹配");
        }
        if (!isLoop) {//不循环的情况
            if (preCurrentIndex < 0) {
                preCurrentIndex = 0;
            }
            if (preCurrentIndex > adapter.getItemsCount() - 1) {
                preCurrentIndex = adapter.getItemsCount() - 1;
            }
        } else {//循环
            if (preCurrentIndex < 0) {//举个例子:如果总数是5,preCurrentIndex = -1,那么preCurrentIndex按循环来说,其实是0的上面,也就是4的位置
                preCurrentIndex = adapter.getItemsCount() + preCurrentIndex;
            }
            if (preCurrentIndex > adapter.getItemsCount() - 1) {//同理上面,自己脑补一下
                preCurrentIndex = preCurrentIndex - adapter.getItemsCount();
            }
        }
        //跟滚动流畅度有关,总滑动距离与每个item高度取余,即并不是一格格的滚动,每个item不一定滚到对应Rect里的,这个item对应格子的偏移值
        float itemHeightOffset = (totalScrollY % itemHeight);
        //绘制中间两条横线
        if (dividerType == DividerType.WRAP) {//横线长度仅包裹内容
            float startX;
            float endX;
            if (TextUtils.isEmpty(label)) {//隐藏Label的情况
                startX = (measuredWidth - maxTextWidth) / 2 - 12;
            } else {
                startX = (measuredWidth - maxTextWidth) / 4 - 12;
            }
            if (startX <= 0) {//如果超过了WheelView的边缘
                startX = 10;
            }
            endX = measuredWidth - startX;
            canvas.drawLine(startX, firstLineY, endX, firstLineY, paintIndicator);
            canvas.drawLine(startX, secondLineY, endX, secondLineY, paintIndicator);
        } else if (dividerType == DividerType.CIRCLE) {
            //分割线为圆圈形状
            paintIndicator.setStyle(Paint.Style.STROKE);
            paintIndicator.setStrokeWidth(dividerWidth);
            float startX;
            float endX;
            if (TextUtils.isEmpty(label)) {//隐藏Label的情况
                startX = (measuredWidth - maxTextWidth) / 2f - 12;
            } else {
                startX = (measuredWidth - maxTextWidth) / 4f - 12;
            }
            if (startX <= 0) {//如果超过了WheelView的边缘
                startX = 10;
            }
            endX = measuredWidth - startX;
            //半径始终以宽高中最大的来算
            float radius = Math.max((endX - startX), itemHeight) / 1.8f;
            canvas.drawCircle(measuredWidth / 2f, measuredHeight / 2f, radius, paintIndicator);
        } else {
            canvas.drawLine(0.0F, firstLineY, measuredWidth, firstLineY, paintIndicator);
            canvas.drawLine(0.0F, secondLineY, measuredWidth, secondLineY, paintIndicator);
        }
        //只显示选中项Label文字的模式,并且Label文字不为空,则进行绘制
        if (!TextUtils.isEmpty(label) && isCenterLabel) {
            //绘制文字,靠右并留出空隙
            int drawRightContentStart = measuredWidth - getTextWidth(paintCenterText, label);
            canvas.drawText(label, drawRightContentStart - CENTER_CONTENT_OFFSET, centerY, paintCenterText);
        }
        // 设置数组中每个元素的值
        int counter = 0;
        while (counter < itemsVisible) {
            Object showText;
            int index = preCurrentIndex - (itemsVisible / 2 - counter);//索引值,即当前在控件中间的item看作数据源的中间,计算出相对源数据源的index值
            //判断是否循环,如果是循环数据源也使用相对循环的position获取对应的item值,如果不是循环则超出数据源范围使用""空白字符串填充,在界面上形成空白无数据的item项
            if (isLoop) {
                index = getLoopMappingIndex(index);
                showText = adapter.getItem(index);
            } else if (index < 0) {
                showText = "";
            } else if (index > adapter.getItemsCount() - 1) {
                showText = "";
            } else {
                showText = adapter.getItem(index);
            }
            canvas.save();
            // 弧长 L = itemHeight * counter - itemHeightOffset
            // 求弧度 α = L / r  (弧长/半径) [0,π]
            double radian = ((itemHeight * counter - itemHeightOffset)) / radius;
            // 弧度转换成角度(把半圆以Y轴为轴心向右转90度,使其处于第一象限及第四象限
            // angle [-90°,90°]
            float angle = (float) (90D - (radian / Math.PI) * 180D);//item第一项,从90度开始,逐渐递减到 -90度
            // 计算取值可能有细微偏差,保证负90°到90°以外的不绘制
            if (angle > 90F || angle < -90F) {
                canvas.restore();
            } else {
                //获取内容文字
                String contentText;
                //如果是label每项都显示的模式,并且item内容不为空、label 也不为空
                if (!isCenterLabel && !TextUtils.isEmpty(label) && !TextUtils.isEmpty(getContentText(showText))) {
                    contentText = getContentText(showText) + label;
                } else {
                    contentText = getContentText(showText);
                }
                // 根据当前角度计算出偏差系数,用以在绘制时控制文字的 水平移动 透明度 倾斜程度.
                float offsetCoefficient = (float) Math.pow(Math.abs(angle) / 90f, 2.2);
                reMeasureTextSize(contentText);
                //计算开始绘制的位置
                measuredCenterContentStart(contentText);
                measuredOutContentStart(contentText);
                float translateY = (float) (radius - Math.cos(radian) * radius - (Math.sin(radian) * maxTextHeight) / 2D);
                //根据Math.sin(radian)来更改canvas坐标系原点,然后缩放画布,使得文字高度进行缩放,形成弧形3d视觉差
                canvas.translate(0.0F, translateY);
                if (translateY <= firstLineY && maxTextHeight + translateY >= firstLineY) {
                    // 条目经过第一条线
                    canvas.save();
                    canvas.clipRect(0, 0, measuredWidth, firstLineY - translateY);
                    canvas.scale(1.0F, (float) Math.sin(radian) * SCALE_CONTENT);
                    setOutPaintStyle(offsetCoefficient, angle);
                    canvas.drawText(contentText, drawOutContentStart, maxTextHeight, paintOuterText);
                    canvas.restore();
                    canvas.save();
                    canvas.clipRect(0, firstLineY - translateY, measuredWidth, (int) (itemHeight));
                    canvas.scale(1.0F, (float) Math.sin(radian) * 1.0F);
                    canvas.drawText(contentText, drawCenterContentStart, maxTextHeight - CENTER_CONTENT_OFFSET, paintCenterText);
                    canvas.restore();
                } else if (translateY <= secondLineY && maxTextHeight + translateY >= secondLineY) {
                    // 条目经过第二条线
                    canvas.save();
                    canvas.clipRect(0, 0, measuredWidth, secondLineY - translateY);
                    canvas.scale(1.0F, (float) Math.sin(radian) * 1.0F);
                    canvas.drawText(contentText, drawCenterContentStart, maxTextHeight - CENTER_CONTENT_OFFSET, paintCenterText);
                    canvas.restore();
                    canvas.save();
                    canvas.clipRect(0, secondLineY - translateY, measuredWidth, (int) (itemHeight));
                    canvas.scale(1.0F, (float) Math.sin(radian) * SCALE_CONTENT);
                    setOutPaintStyle(offsetCoefficient, angle);
                    canvas.drawText(contentText, drawOutContentStart, maxTextHeight, paintOuterText);
                    canvas.restore();
                } else if (translateY >= firstLineY && maxTextHeight + translateY <= secondLineY) {
                    // 中间条目
                    // canvas.clipRect(0, 0, measuredWidth, maxTextHeight);
                    //让文字居中
                    float Y = maxTextHeight - CENTER_CONTENT_OFFSET;//因为圆弧角换算的向下取值,导致角度稍微有点偏差,加上画笔的基线会偏上,因此需要偏移量修正一下
                    canvas.drawText(contentText, drawCenterContentStart, Y, paintCenterText);
                    //设置选中项
                    selectedItem = preCurrentIndex - (itemsVisible / 2 - counter);
                } else {
                    // 其他条目
                    canvas.save();
                    canvas.clipRect(0, 0, measuredWidth, (int) (itemHeight));
                    canvas.scale(1.0F, (float) Math.sin(radian) * SCALE_CONTENT);
                    setOutPaintStyle(offsetCoefficient, angle);
                    // 控制文字水平偏移距离
                    canvas.drawText(contentText, drawOutContentStart + textXOffset * offsetCoefficient, maxTextHeight, paintOuterText);
                    canvas.restore();
                }
                canvas.restore();
                paintCenterText.setTextSize(textSize);
            }
            counter++;
        }
    }
    //设置文字倾斜角度,透明度
    private void setOutPaintStyle(float offsetCoefficient, float angle) {
        // 控制文字倾斜角度
        float DEFAULT_TEXT_TARGET_SKEW_X = 0.5f;
        int multiplier = 0;
        if (textXOffset > 0) {
            multiplier = 1;
        } else if (textXOffset < 0) {
            multiplier = -1;
        }
        paintOuterText.setTextSkewX(multiplier * (angle > 0 ? -1 : 1) * DEFAULT_TEXT_TARGET_SKEW_X * offsetCoefficient);
        // 控制透明度
        int alpha = isAlphaGradient ? (int) ((90F - Math.abs(angle)) / 90f * 255) : 255;
        // Log.d("WheelView", "alpha:" + alpha);
        paintOuterText.setAlpha(alpha);
    }
    /**
     * reset the size of the text Let it can fully display
     *
     * @param contentText item text content.
     */
    private void reMeasureTextSize(String contentText) {
        Rect rect = new Rect();
        paintCenterText.getTextBounds(contentText, 0, contentText.length(), rect);
        int width = rect.width();
        int size = textSize;
        while (width > measuredWidth) {
            size--;
            //设置2条横线中间的文字大小
            paintCenterText.setTextSize(size);
            paintCenterText.getTextBounds(contentText, 0, contentText.length(), rect);
            width = rect.width();
        }
        //设置2条横线外面的文字大小
        paintOuterText.setTextSize(size);
    }
    //递归计算出对应的index
    private int getLoopMappingIndex(int index) {
        if (index < 0) {
            index = index + adapter.getItemsCount();
            index = getLoopMappingIndex(index);
        } else if (index > adapter.getItemsCount() - 1) {
            index = index - adapter.getItemsCount();
            index = getLoopMappingIndex(index);
        }
        return index;
    }
    /**
     * 获取所显示的数据源
     *
     * @param item data resource
     * @return 对应显示的字符串
     */
    private String getContentText(Object item) {
        if (item == null) {
            return "";
        } else if (item instanceof IPickerViewData) {
            return ((IPickerViewData) item).getPickerViewText();
        } else if (item instanceof Integer) {
            //如果为整形则最少保留两位数.
            return getFixNum((int) item);
        }
        return item.toString();
    }
    private String getFixNum(int timeNum) {
        return timeNum >= 0 && timeNum < 10 ? TIME_NUM[timeNum] : String.valueOf(timeNum);
    }
    private void measuredCenterContentStart(String content) {
        Rect rect = new Rect();
        paintCenterText.getTextBounds(content, 0, content.length(), rect);
        switch (mGravity) {
            case Gravity.CENTER://显示内容居中
                if (isOptions || label == null || label.equals("") || !isCenterLabel) {
                    drawCenterContentStart = (int) ((measuredWidth - rect.width()) * 0.5);
                } else {//只显示中间label时,时间选择器内容偏左一点,留出空间绘制单位标签
                    drawCenterContentStart = (int) ((measuredWidth - rect.width()) * 0.25);
                }
                break;
            case Gravity.LEFT:
                drawCenterContentStart = 0;
                break;
            case Gravity.RIGHT://添加偏移量
                drawCenterContentStart = measuredWidth - rect.width() - (int) CENTER_CONTENT_OFFSET;
                break;
        }
    }
    private void measuredOutContentStart(String content) {
        Rect rect = new Rect();
        paintOuterText.getTextBounds(content, 0, content.length(), rect);
        switch (mGravity) {
            case Gravity.CENTER:
                if (isOptions || label == null || label.equals("") || !isCenterLabel) {
                    drawOutContentStart = (int) ((measuredWidth - rect.width()) * 0.5);
                } else {//只显示中间label时,时间选择器内容偏左一点,留出空间绘制单位标签
                    drawOutContentStart = (int) ((measuredWidth - rect.width()) * 0.25);
                }
                break;
            case Gravity.LEFT:
                drawOutContentStart = 0;
                break;
            case Gravity.RIGHT:
                drawOutContentStart = measuredWidth - rect.width() - (int) CENTER_CONTENT_OFFSET;
                break;
        }
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        this.widthMeasureSpec = widthMeasureSpec;
        reMeasure();
        setMeasuredDimension(measuredWidth, measuredHeight);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean eventConsumed = gestureDetector.onTouchEvent(event);
        boolean isIgnore = false;//超过边界滑动时,不再绘制UI。
        float top = -initPosition * itemHeight;
        float bottom = (adapter.getItemsCount() - 1 - initPosition) * itemHeight;
        float ratio = 0.25f;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startTime = System.currentTimeMillis();
                cancelFuture();
                previousY = event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                float dy = previousY - event.getRawY();
                previousY = event.getRawY();
                totalScrollY = totalScrollY + dy;
                // normal mode。
                if (!isLoop) {
                    if ((totalScrollY - itemHeight * ratio < top && dy < 0)
                            || (totalScrollY + itemHeight * ratio > bottom && dy > 0)) {
                        //快滑动到边界了,设置已滑动到边界的标志
                        totalScrollY -= dy;
                        isIgnore = true;
                    } else {
                        isIgnore = false;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            default:
                if (!eventConsumed) {//未消费掉事件
                    /**
                     *@describe <关于弧长的计算>
                     *
                     * 弧长公式: L = α*R
                     * 反余弦公式:arccos(cosα) = α
                     * 由于之前是有顺时针偏移90度,
                     * 所以实际弧度范围α2的值 :α2 = π/2-α    (α=[0,π] α2 = [-π/2,π/2])
                     * 根据正弦余弦转换公式 cosα = sin(π/2-α)
                     * 代入,得: cosα = sin(π/2-α) = sinα2 = (R - y) / R
                     * 所以弧长 L = arccos(cosα)*R = arccos((R - y) / R)*R
                     */
                    float y = event.getY();
                    double L = Math.acos((radius - y) / radius) * radius;
                    //item0 有一半是在不可见区域,所以需要加上 itemHeight / 2
                    int circlePosition = (int) ((L + itemHeight / 2) / itemHeight);
                    float extraOffset = (totalScrollY % itemHeight + itemHeight) % itemHeight;
                    //已滑动的弧长值
                    mOffset = (int) ((circlePosition - itemsVisible / 2) * itemHeight - extraOffset);
                    if ((System.currentTimeMillis() - startTime) > 120) {
                        // 处理拖拽事件
                        smoothScroll(ACTION.DAGGLE);
                    } else {
                        // 处理条目点击事件
                        smoothScroll(ACTION.CLICK);
                    }
                }
                break;
        }
        if (!isIgnore && event.getAction() != MotionEvent.ACTION_DOWN) {
            invalidate();
        }
        return true;
    }
    public int getItemsCount() {
        return adapter != null ? adapter.getItemsCount() : 0;
    }
    public void setLabel(String label) {
        this.label = label;
    }
    public void isCenterLabel(boolean isCenterLabel) {
        this.isCenterLabel = isCenterLabel;
    }
    public void setGravity(int gravity) {
        this.mGravity = gravity;
    }
    public int getTextWidth(Paint paint, String str) { //calculate text width
        int iRet = 0;
        if (str != null && str.length() > 0) {
            int len = str.length();
            float[] widths = new float[len];
            paint.getTextWidths(str, widths);
            for (int j = 0; j < len; j++) {
                iRet += (int) Math.ceil(widths[j]);
            }
        }
        return iRet;
    }
    public void setIsOptions(boolean options) {
        isOptions = options;
    }
    public void setTextColorOut(int textColorOut) {
        this.textColorOut = textColorOut;
        paintOuterText.setColor(this.textColorOut);
    }
    public void setTextColorCenter(int textColorCenter) {
        this.textColorCenter = textColorCenter;
        paintCenterText.setColor(this.textColorCenter);
    }
    public void setTextXOffset(int textXOffset) {
        this.textXOffset = textXOffset;
        if (textXOffset != 0) {
            paintCenterText.setTextScaleX(1.0f);
        }
    }
    public void setDividerWidth(int dividerWidth) {
        this.dividerWidth = dividerWidth;
        paintIndicator.setStrokeWidth(dividerWidth);
    }
    public void setDividerColor(int dividerColor) {
        this.dividerColor = dividerColor;
        paintIndicator.setColor(dividerColor);
    }
    public void setDividerType(DividerType dividerType) {
        this.dividerType = dividerType;
    }
    public void setLineSpacingMultiplier(float lineSpacingMultiplier) {
        if (lineSpacingMultiplier != 0) {
            this.lineSpacingMultiplier = lineSpacingMultiplier;
            judgeLineSpace();
        }
    }
    public boolean isLoop() {
        return isLoop;
    }
    public float getTotalScrollY() {
        return totalScrollY;
    }
    public void setTotalScrollY(float totalScrollY) {
        this.totalScrollY = totalScrollY;
    }
    public float getItemHeight() {
        return itemHeight;
    }
    public int getInitPosition() {
        return initPosition;
    }
    @Override
    public Handler getHandler() {
        return handler;
    }
}
wheelview/src/main/res/values/attrs.xml
New file
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="pickerview">
        <attr name="wheelview_gravity">
            <enum name="center" value="17"/>
            <enum name="left" value="3"/>
            <enum name="right" value="5"/>
        </attr>
        <attr name="wheelview_textSize" format="dimension"/>
        <attr name="wheelview_textColorOut" format="color"/>
        <attr name="wheelview_textColorCenter" format="color"/>
        <attr name="wheelview_dividerColor" format="color"/>
        <attr name="wheelview_dividerWidth" format="dimension"/>
        <attr name="wheelview_lineSpacingMultiplier" format="float"/>
    </declare-styleable>
</resources>
wheelview/src/main/res/values/colors.xml
New file
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="pickerview_timebtn_nor">#057dff</color>
    <color name="pickerview_timebtn_pre">#c2daf5</color>
    <color name="pickerview_bg_topbar">#f5f5f5</color>
    <color name="pickerview_topbar_title">#000000</color>
    <color name="pickerview_wheelview_textcolor_out">#a8a8a8</color>
    <color name="pickerview_wheelview_textcolor_center">#2a2a2a</color>
    <color name="pickerview_wheelview_textcolor_divider">#d5d5d5</color>
    <color name="pickerview_bgColor_overlay">#60000000</color>
    <color name="pickerview_bgColor_default">#FFFFFFFF</color>
</resources>
wheelview/src/main/res/values/dimens.xml
New file
@@ -0,0 +1,13 @@
<resources>
    <!-- 顶部按钮栏高度 -->
    <dimen name="pickerview_topbar_height">44dp</dimen>
    <!-- 顶部按钮padding -->
    <dimen name="pickerview_topbar_padding">20dp</dimen>
    <!-- 顶部按钮文字大小 -->
    <dimen name="pickerview_topbar_btn_textsize">17sp</dimen>
    <dimen name="pickerview_topbar_title_textsize">18sp</dimen>
    <!-- 选项文字大小 -->
    <dimen name="pickerview_textsize">20sp</dimen>
</resources>
wheelview/src/main/res/values/strings.xml
New file
@@ -0,0 +1,3 @@
<resources>
    <string name="app_name">wheelview</string>
</resources>