Android进阶之路 - AndroidPicker 快速实现多样化日期选择器、地址选择器

本文详细介绍 AndroidPicker 框架的应用,包括日期选择器、地址选择器等组件的配置与使用方法,并提供 2017 年与 2022 年不同版本的对比与实践案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

日期选择器,项目中不少用,网上也有很多,包括我之前也抄过一个,但是没有这个漂亮,毕竟我也是在进步嘛,这次使用的三方框架为AndroidPicker,一开始看了几篇文章介绍,包括作者介绍的使用方式都不得其解,好在最后是解决了,同时简单封装了一下,大家请直接看下文把 ~

地址关联Blog

AndroidPicker 本身是一款很优秀的三方框架,在2017年的时候就已经有很多扩展功能,其中地址选择器时间选择器只是很小的一部分,根据原作者描述,还包含以下选择器

  • 日期及时间选择器(可用于出生日期、营业时间等)
  • 单项选择器(可用于性别、民族、职业、学历、星座等)
  • 二三级联动选择器(可用于车牌号、基金定投日期等)
  • 城市地址选择器(分省级、地市级及区县级)
  • 数字选择器(可用于年龄、身高、体重、温度等)
  • 日历选日期择器(可用于酒店及机票预定日期)
  • 颜色选择器文件及目录选择器图片选择器等……

我是有幸在2022年的时候又用到了AndroidPicker,回想之前写过该框架的类似Blog,发现早之前(2017)的写法早就过时了,所以在补入一版吧… 下方关于2017年的部分,可以直接无视!!!~

2017年

我记得以前应该是Git图,不知道csdn后面怎么给转存的,效果都让人看不了,无语...

这里写图片描述

准备工作

如使用中出现错误,请看最后的错误点提醒

build 引入

latest.integration 是根据框架的版本自行调试的

  compile 'cn.qqtheme.framework:WheelPicker:latest.integration'

插入动画

  1. res文件下创建anim文件夹

popup_in.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="500"
        android:fromYDelta="100%p"
        android:toYDelta="0" />
    <alpha
        android:duration="500"
        android:fromAlpha="0"
        android:toAlpha="1" />
</set>

popup_out.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="1000"
        android:fromYDelta="0"
        android:toYDelta="50%p" />
    <alpha
        android:duration="500"
        android:fromAlpha="1"
        android:toAlpha="0" />
</set>
  1. value下的style内封装动画style
  <style name="Animation_CustomPopup" parent="@android:style/Animation">
        <item name="android:windowEnterAnimation">@anim/popup_in</item>
        <item name="android:windowExitAnimation">@anim/popup_out</item>
  </style>
使用方式

DateListener(使用此接口传输数据 - 接口回调)

package com.bakheet.effect.picker;

/**
 * @author by YongLiu on 2017/11/14.
 */

public interface DateListener {
    void setYear(String year);

    void setMonth(String month);

    void setDay(String day);

    void setMouthDate(String year, String month);

    void setYearDate(String year, String month, String day);

}

MainActivity

package com.bakheet.effect.picker;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView mContent;
    private TextView mBtn;
    private TextView mBtnMonth;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBtn = (TextView) findViewById(R.id.btn);
        mContent = (TextView) findViewById(R.id.content);
        mBtnMonth = (TextView) findViewById(R.id.btn_month);

        /**
         * 年月日
         * */
        mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                DateUtil.setYearDate(MainActivity.this, new DateListener() {
                    @Override
                    public void setYear(String year) {

                    }

                    @Override
                    public void setMonth(String month) {

                    }

                    @Override
                    public void setDay(String day) {

                    }

                    @Override
                    public void setMouthDate(String year, String month) {

                    }

                    @Override
                    public void setYearDate(String year, String month, String day) {
                        mContent.setText(year + "-" + month + "-" + day);
                    }
                });
            }
        });

        /**
         * 年月
         * */
        mBtnMonth.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                DateUtil.setMonthDate(MainActivity.this, new DateListener() {
                    @Override
                    public void setYear(String year) {

                    }

                    @Override
                    public void setMonth(String month) {

                    }

                    @Override
                    public void setDay(String day) {

                    }

                    @Override
                    public void setMouthDate(String year, String month) {
                        mContent.setText(year + "-" + month);
                    }

                    @Override
                    public void setYearDate(String year, String month, String day) {

                    }
                });
            }
        });
    }
}

activity_main

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.bakheet.effect.picker.MainActivity">

    <TextView
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="8dp"
        android:text="弹出日期框 - 年月" />

    <View
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:background="@color/colorPrimaryDark" />

    <TextView
        android:id="@+id/btn_month"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="8dp"
        android:text="弹出日期框 - 年月日" />

    <View
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:background="@color/colorPrimaryDark" />

    <TextView
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="日期展示区" />
</LinearLayout>

DateUtil (简单封装,方便使用)

package com.bakheet.effect.picker;

import android.app.Activity;
import android.text.format.Time;

import cn.qqtheme.framework.picker.DatePicker;
import cn.qqtheme.framework.picker.DateTimePicker;

/**
 * Created by YongLiu on 2017/11/14.
 * @author
 */

public class DateUtil {

    /**
     * 年-月
     */
    static void setMonthDate(final Activity mContext,final DateListener dateListener){
        DatePicker picker = new DatePicker(mContext, DateTimePicker.YEAR_MONTH);
        //获取当前时间
        Time time = new Time();
        time.setToNow();
        int year = time.year;
        int month = time.month;
        //设置时间区间
        picker.setRange(2000,2020);
        //设置默认显示时间
        picker.setSelectedItem(year,month+1);
        //加入动画
        picker.setAnimationStyle(R.style.Animation_CustomPopup);
        //回传数据
        picker.setOnDatePickListener(new DatePicker.OnYearMonthPickListener() {
            @Override
            public void onDatePicked(String year, String month) {
                dateListener.setMouthDate(year,month);
            }
        });
        picker.show();
    }

    /**
     * 年-月-日
     */
    static void setYearDate(final Activity mContext,final DateListener dateListener){
        DatePicker picker = new DatePicker(mContext);
        //获取当前时间
        Time time = new Time();
        time.setToNow();
        int year = time.year;
        int month = time.month;
        int monthDay = time.monthDay;
        //设置时间区间
        picker.setRange(2000,2020);
        //设置默认显示时间
        picker.setSelectedItem(year,month+1,monthDay);
        //加入动画
        picker.setAnimationStyle(R.style.Animation_CustomPopup);
        //回传数据
        picker.setOnDatePickListener(new DatePicker.OnYearMonthDayPickListener() {
            @Override
            public void onDatePicked(String year, String month, String day) {
                dateListener.setYearDate(year,month,day);
            }
        });
        picker.show();
    }

	/**
     * 年-月  最后可选日期为当日
     */
    public static void setMonthDate(final Activity mContext, String startYear, String startMonth, boolean dynamicStartTime, final DateListener dateListener) {
        DatePicker picker = new DatePicker(mContext, DatePicker.YEAR_MONTH);
        //获取当前时间
        Time time = new Time();
        time.setToNow();
        int currentYear = time.year;
        int currentMonth = time.month;
        TLogUtils.e("currentMonth =" + currentMonth);

        //设置时间区间
        if (dynamicStartTime) {
            //动态的开始时间
            picker.setRangeStart(ToolUtil.getIntParseString(startYear), ToolUtil.getIntParseString(startMonth));
            picker.setRangeEnd(2050, 1);
        } else {
            //动态的结束时间
            picker.setRangeStart(2000, 1);
            picker.setRangeEnd(currentYear, currentMonth + 1);
        }
        //设置时间区间
        //        picker.setRange(2000, year);
        //循环
        //        picker.setCycleDisable(false);
        //设置默认显示时间
        picker.setSelectedItem(currentYear, currentMonth + 1);
        //加入动画
        picker.setAnimationStyle(R.style.Animation_CustomPopup);
        //设置取消按钮文字颜色
        picker.setCancelTextColor(ContextCompat.getColor(mContext, R.color.gray1));
        //设置取消按钮文字大小
        picker.setCancelTextSize(14);
        //设置顶部标题栏下划线颜色
        picker.setTopLineColor(ContextCompat.getColor(mContext, R.color.white1));
        //设置分割线颜色
        picker.setDividerColor(ContextCompat.getColor(mContext, R.color.white1));
        //设置确定按钮文字颜色
        picker.setSubmitTextColor(ContextCompat.getColor(mContext, R.color.blue2));
        picker.setTextColor(ContextCompat.getColor(mContext, R.color.blue2));
        picker.setSubmitTextSize(14);
        //设置标题文字颜色
        picker.setTitleTextColor(ContextCompat.getColor(mContext, R.color.gray5));
        //设置标题文字
        picker.setTitleText("选择月份");
        //设置标题文字大小
        picker.setTitleTextSize(14);
        //回传数据
        picker.setOnDatePickListener(new DatePicker.OnYearMonthPickListener() {
            @Override
            public void onDatePicked(String year, String month) {
                dateListener.setMouthDate(year, month);
            }
        });
        picker.show();
    }
}

	/**
     * 选择税率 - 偏向自定义样式
     */
    public static void setRate(final Activity mContext, OptionPicker.OnOptionPickListener onOptionPickListener) {
        String[] strings = {"0%", "3%", "17%"};
        OptionPicker picker = new OptionPicker(mContext, strings);
        picker.setSelectedIndex(0);
        picker.setTextSize(22);
        picker.setItemWidth(160);
        //设置取消按钮文字颜色
        picker.setCancelTextColor(ContextCompat.getColor(mContext, R.color.gray1));
        //设置取消按钮文字大小
        picker.setCancelTextSize(14);
        //设置顶部标题栏下划线颜色
        picker.setTopLineColor(ContextCompat.getColor(mContext, R.color.white1));
        //设置分割线颜色
        picker.setDividerColor(ContextCompat.getColor(mContext, R.color.white1));
        //设置确定按钮文字颜色
        picker.setSubmitTextColor(ContextCompat.getColor(mContext, R.color.blue2));
        picker.setTextColor(ContextCompat.getColor(mContext, R.color.blue2));
        picker.setSubmitTextSize(14);
        //设置标题文字颜色
        picker.setTitleTextColor(ContextCompat.getColor(mContext, R.color.gray5));
        //设置标题文字
        picker.setTitleText("选择税率");
        //设置标题文字大小
        picker.setTitleTextSize(14);
        picker.setOnOptionPickListener(onOptionPickListener);
        picker.show();
    }


	 /**
     * 地址 - 因为加载本地的json文件,所以有时候会有一些卡顿
     */
    public static void setPlace(Activity mContext, final PlaceListener placeListener) {
        ArrayList<Province> data = new ArrayList<>();
        String json = AssetsUtils.getStringFromAssert(mContext, "city.json");
        data.addAll(JSON.parseArray(json, Province.class));
        AddressPicker picker = new AddressPicker(mContext, data);
        picker.setSelectedItem("上海", "上海市", "浦东新区");
        //设置取消按钮文字颜色
        picker.setCancelTextColor(ContextCompat.getColor(mContext, R.color.gray1));
        //设置取消按钮文字大小
        picker.setCancelTextSize(14);
        //设置顶部标题栏下划线颜色
        picker.setTopLineColor(ContextCompat.getColor(mContext, R.color.white1));
        //设置分割线颜色
        picker.setDividerColor(ContextCompat.getColor(mContext, R.color.white1));
        //设置确定按钮文字颜色
        picker.setSubmitTextColor(ContextCompat.getColor(mContext, R.color.blue2));
        picker.setTextColor(ContextCompat.getColor(mContext, R.color.blue2));
        picker.setSubmitTextSize(14);
        //设置标题文字颜色
        picker.setTitleTextColor(ContextCompat.getColor(mContext, R.color.gray5));
        //设置标题文字
        picker.setTitleText("选择城市区域");
        //设置标题文字大小
        picker.setTitleTextSize(14);
        picker.setOnAddressPickListener(new AddressPicker.OnAddressPickListener() {
            @Override
            public void onAddressPicked(Province province, City city, County county) {
                placeListener.setProvince(province.getAreaName());
                //城市
                placeListener.setCity(city.getAreaName());
                //区县(如果设定了两级联动,那么该项返回空)
                placeListener.setDistrict(county.getAreaName());
                //全部地址
                placeListener.setAllPlace(province.getAreaName() + " " + city.getAreaName() + " " + county.getAreaName());
            }
        });
        picker.show();
    }

注意

  1. 使用 地址选择器时,需要在 assets 文件夹下加入 “city.json” - city.json地址

  2. 因为要数据回调,需要一个接口

package com.bakheet.garage.mine.listener;

/**
 * author  YongLiu
 * date  2017/12/14.
 * desc:
 */

public interface PlaceListener {
    void setProvince(String province);

    void setCity(String city);

    void setDistrict(String district);

    void setAllPlace(String allPlace);
}
错误提示

如出现 Error:Execution failed for task ':app:processDebugManifest'. > Manifest merger failed with

build

    //注释framework:WheelPicker:latest这个引用,引用下方新的AndroidPicker
    //compile 'cn.qqtheme.framework:WheelPicker:latest.integration'
    compile 'cn.qqtheme.framework:AndroidPicker:latest.integration'

同时修改DateUtil内的 DateTimePicker 为 DatePicker(可参考此处


2022年

build(app) - 加入

    //生日、地址、日期等选择器
    implementation 'com.github.gzu-liyujiang.AndroidPicker:Common:4.1.6'
    implementation 'com.github.gzu-liyujiang.AndroidPicker:AddressPicker:4.1.6'
    implementation 'com.github.gzu-liyujiang.AndroidPicker:WheelPicker:4.1.6'
    implementation 'com.github.gzu-liyujiang.AndroidPicker:ImagePicker:4.1.6'

这是我在新项目某个类中用到的AndroidPicker方法,涉及单项选择器(男、女性别)、地址选择器、日期选择器、时间(日期)选择器,因为我用的的Kt+MVVM+ViewBind等等,所以看对应选择器方法即可 ~

伪代码示例

import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import androidx.lifecycle.Observer
import com.blankj.utilcode.util.SPUtils
import com.blankj.utilcode.util.ToastUtils
import com.github.gzuliyujiang.imagepicker.ActivityBuilder
import com.github.gzuliyujiang.imagepicker.CropImageView
import com.github.gzuliyujiang.imagepicker.ImagePicker
import com.github.gzuliyujiang.imagepicker.PickCallback
import com.github.gzuliyujiang.wheelpicker.BirthdayPicker
import com.github.gzuliyujiang.wheelpicker.SexPicker
import com.github.gzuliyujiang.wheelpicker.contract.OnDatePickedListener
import com.github.gzuliyujiang.wheelpicker.contract.OnOptionPickedListener
import com.google.android.material.bottomsheet.BottomSheetDialog
import org.koin.androidx.viewmodel.ext.android.viewModel
import java.text.SimpleDateFormat

/**
 * author: ly
 * dsc: 信息完善
 */
class PerfectionActivity : BaseActivity<ActivityPerfectionBinding>(),
    View.OnClickListener, OnDatePickedListener, OnOptionPickedListener, OnAddressPickedListener {
    var updateBirthday: String = ""
    private var sexDate = true

    override fun initView() {
        binding.csGenderParent.setOnClickListener(this)
        binding.csCityParent.setOnClickListener(this)
        binding.csBirthdayParent.setOnClickListener(this)
    }

    override fun initData() {}
    override fun observerData() {}

    override fun onClick(v: View?) {
        when (v?.id) {
            binding.csGenderParent.id -> genderDialog()
            binding.csCityParent.id -> { addressDialog()}
            binding.csBirthdayParent.id -> {
                var tvBirthday = binding.tvBirthday.text.toString()
                if (tvBirthday.isNullOrEmpty()) {
                    tvBirthday = "2022-3-14"
                }
                birthdayDialog(tvBirthday)
            }
        }
    }

	 //地址选择器
     fun addressDialog() {
         val picker = AddressPicker(this)
         picker.setAddressMode(AddressMode.PROVINCE_CITY_COUNTY)
         picker.setDefaultValue("上海市", "上海市", "黄浦区")
         picker.setOnAddressPickedListener(this);
         picker.wheelLayout.setOnLinkageSelectedListener(object : OnLinkageSelectedListener {
             override fun onLinkageSelected(first: Any?, second: Any?, third: Any?) {
                 picker.getTitleView().setText(
                     String.format(
                         "%s%s%s",
                         picker.getFirstWheelView().formatItem(first),
                         picker.getSecondWheelView().formatItem(second),
                         picker.getThirdWheelView().formatItem(third)
                     )
                 );
             }
         })
         picker.show();
     }

	//性别单项选择器
    private fun genderDialog() {
        val picker = SexPicker(this)
        picker.setBodyWidth(140)
        picker.setIncludeSecrecy(false)
        if (sexDate) {
            picker.setDefaultValue("男")
        } else {
            picker.setDefaultValue("女")
        }
        picker.setOnOptionPickedListener(this)
        picker.wheelLayout.setOnOptionSelectedListener { position, item -> }
        picker.show()
    }

	//日期选择器
    private fun birthdayDialog(birthday: String = "2022-3-14") {
        val picker = BirthdayPicker(this)
        var split = birthday.split("-".toRegex())
        picker.setDefaultValue(split[0].toInt(), split[1].toInt(), split[2].toInt())
        picker.setOnDatePickedListener(this)
        picker.wheelLayout.setResetWhenLinkage(false)
        picker.show()
    }
    
	//地址选择器 - 回调
     override fun onAddressPicked(
         province: ProvinceEntity?,
         city: CityEntity?,
         county: CountyEntity?
     ) {
         binding.tvAddress.setText(province?.name + city?.name + county?.name)
         
     }

	//日期选择器 - 回调(已优化)
    override fun onDatePicked(year: Int, month: Int, day: Int) {
        //最简单 - binding.tvBirthday.setText("$year-$month-$day")
        var tmpMonth = ""
        var tmpDay = ""
        tmpMonth = if (month < 10) {
            "0" + month
        } else {
            month.toString()
        }
        tmpDay = if (day < 10) {
            "0" + day
        } else {
            day.toString()
        }

        var date = "$year-$tmpMonth-$tmpDay"
        val sdf = SimpleDateFormat("yyyy-MM-dd")
        val millionSeconds = sdf.parse(date).time.toString()
        //对应时间戳,主要用于传后台
        var uploadTime = millionSeconds.substring(0, millionSeconds.length - 3)
        Log.e("tag", millionSeconds)
        Log.e("tag", uploadTime)
        updateBirthday = uploadTime
        binding.tvBirthday.text = date
    }

	//性别选择器 - 回调(已优化)
    override fun onOptionPicked(position: Int, item: Any?) {
        if (position == 0) {
            sexDate = true
            binding.tvGender.text = "男"
        } else {
            sexDate = false
            binding.tvGender.text = "女"
        }
    }
}
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

远方那座山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值