自定义时间范围筛选
This commit is contained in:
parent
d610ef5d53
commit
592918d6ce
@ -54,4 +54,5 @@ dependencies {
|
|||||||
kapt (libs.compiler)
|
kapt (libs.compiler)
|
||||||
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||||
implementation ("com.google.android.material:material:1.13.0")
|
implementation ("com.google.android.material:material:1.13.0")
|
||||||
|
implementation(project(":pickerview"))
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package com.ux.video.file.filerecovery
|
package com.ux.video.file.filerecovery
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import org.jaaksi.pickerview.widget.BasePickerView
|
||||||
|
|
||||||
class App: Application() {
|
class App: Application() {
|
||||||
|
|
||||||
@ -10,5 +11,8 @@ class App: Application() {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
mAppContext = this
|
mAppContext = this
|
||||||
|
|
||||||
|
BasePickerView.sDefaultItemSize = 40
|
||||||
|
// BasePickerView.sDefaultDrawIndicator = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -21,10 +21,12 @@ import com.ux.video.file.filerecovery.R
|
|||||||
import com.ux.video.file.filerecovery.base.BaseActivity
|
import com.ux.video.file.filerecovery.base.BaseActivity
|
||||||
import com.ux.video.file.filerecovery.databinding.ActivityMainBinding
|
import com.ux.video.file.filerecovery.databinding.ActivityMainBinding
|
||||||
import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity
|
import com.ux.video.file.filerecovery.main.ScanSelectTypeActivity
|
||||||
|
import com.ux.video.file.filerecovery.photo.DateFilterPopupWindows
|
||||||
|
import com.ux.video.file.filerecovery.photo.DatePickerDialogFragment
|
||||||
import com.ux.video.file.filerecovery.utils.ScanManager
|
import com.ux.video.file.filerecovery.utils.ScanManager
|
||||||
|
|
||||||
class MainActivity : BaseActivity<ActivityMainBinding>() {
|
class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||||
|
private var dialogCustomerDateStart:DatePickerDialogFragment? = null
|
||||||
private var dialogPermission: PermissionDialogFragment? = null
|
private var dialogPermission: PermissionDialogFragment? = null
|
||||||
//是否正确引导用户打开所有文件管理权限
|
//是否正确引导用户打开所有文件管理权限
|
||||||
private var isRequestPermission = false
|
private var isRequestPermission = false
|
||||||
@ -94,9 +96,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
intentCheck()
|
intentCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.btnPermission.setOnClickListener {
|
binding.tvTitle.setOnClickListener {
|
||||||
|
|
||||||
|
|
||||||
|
showDatePicker()
|
||||||
|
|
||||||
}
|
}
|
||||||
binding.btnScanAllPhoto.setOnClickListener {
|
binding.btnScanAllPhoto.setOnClickListener {
|
||||||
@ -242,4 +244,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
binding.layoutPermission.isVisible = false
|
binding.layoutPermission.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun showDatePicker() {
|
||||||
|
// dialogCustomerDateStart = dialogCustomerDateStart?: DateFilterPopupWindows(this){}
|
||||||
|
// dialogCustomerDateStart?.show(supportFragmentManager,"")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,171 @@
|
|||||||
|
package com.ux.video.file.filerecovery.photo
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.PopupWindow
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.ux.video.file.filerecovery.R
|
||||||
|
import com.ux.video.file.filerecovery.databinding.PopwindowsFilterDateBinding
|
||||||
|
import com.ux.video.file.filerecovery.utils.Common
|
||||||
|
import com.ux.video.file.filerecovery.utils.Common.setItemSelect
|
||||||
|
import com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日期筛选弹窗
|
||||||
|
*/
|
||||||
|
class DateFilterPopupWindows(
|
||||||
|
context: Context,
|
||||||
|
var selectedPos: Int,
|
||||||
|
var onClickConfirm: (value: String, showDateDialog: Boolean) -> Unit,
|
||||||
|
var onResetDate: (isStart: Boolean,currentDisplayDate: Date?) -> Unit,
|
||||||
|
var onClickDismiss: () -> Unit
|
||||||
|
) :
|
||||||
|
PopupWindow(context) {
|
||||||
|
|
||||||
|
private val viewBinding: PopwindowsFilterDateBinding =
|
||||||
|
PopwindowsFilterDateBinding.inflate(LayoutInflater.from(context))
|
||||||
|
|
||||||
|
private lateinit var startDate: Date
|
||||||
|
private lateinit var endDate: Date
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
setContentView(viewBinding.root)
|
||||||
|
|
||||||
|
|
||||||
|
width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
|
||||||
|
isFocusable = true
|
||||||
|
isOutsideTouchable = true
|
||||||
|
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||||
|
|
||||||
|
viewBinding.viewMask.setOnClickListener {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
viewBinding.run {
|
||||||
|
tvStartDate.setOnClickListener { onResetDate(true, Common.getDateFromFormat(tvStartDate.text.toString())) }
|
||||||
|
tvEndDate.setOnClickListener { onResetDate(false,Common.getDateFromFormat(tvEndDate.text.toString())) }
|
||||||
|
}
|
||||||
|
|
||||||
|
setData(context.resources.getStringArray(R.array.filter_date), selectedPos)
|
||||||
|
Common.showLog("--------------FilterPopupWindows init")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateSelectPos(index: Int, start: Date, end: Date) {
|
||||||
|
selectedPos = index
|
||||||
|
setPosSelect(selectedPos)
|
||||||
|
if (index == 4) {
|
||||||
|
startDate = start
|
||||||
|
endDate = end
|
||||||
|
viewBinding.run {
|
||||||
|
layoutStartEnd.isVisible = true
|
||||||
|
tvStartDate.text = Common.getChineseFormatDate(start)
|
||||||
|
tvEndDate.text = Common.getChineseFormatDate(end)
|
||||||
|
Common.setItemSelect(layoutStartEnd, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateStartEndDate(start: Date? = null, end: Date? = null) {
|
||||||
|
start?.let { viewBinding.tvStartDate.text = Common.getChineseFormatDate(it) }
|
||||||
|
end?.let { viewBinding.tvEndDate.text = Common.getChineseFormatDate(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setData(
|
||||||
|
list: Array<String>,
|
||||||
|
selectedPos: Int,
|
||||||
|
) {
|
||||||
|
|
||||||
|
list.forEachIndexed { index, item ->
|
||||||
|
viewBinding.run {
|
||||||
|
when (index) {
|
||||||
|
0 -> tvAll.text = item
|
||||||
|
1 -> tv1.text = item
|
||||||
|
2 -> tv6.text = item
|
||||||
|
3 -> tv24.text = item
|
||||||
|
4 -> tvCustomize.text = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
setMutualExclusion()
|
||||||
|
setPosSelect(selectedPos)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun setMutualExclusion() {
|
||||||
|
viewBinding.run {
|
||||||
|
for (i in 0 until parentLayout.childCount) {
|
||||||
|
val child = parentLayout.getChildAt(i)
|
||||||
|
if (child is RelativeLayout) {
|
||||||
|
child.setOnClickListener {
|
||||||
|
//回调选中的文字
|
||||||
|
for (j in 0 until child.childCount) {
|
||||||
|
val other = child.getChildAt(j)
|
||||||
|
if (other is CustomTextView) {
|
||||||
|
other.text.toString().let {
|
||||||
|
if (it == "Customize" && layoutStartEnd.isVisible == false) {
|
||||||
|
//自定义日期
|
||||||
|
onClickConfirm.invoke(it, true)
|
||||||
|
} else {
|
||||||
|
setPosSelect(i)
|
||||||
|
dismiss()
|
||||||
|
onClickConfirm.invoke(it, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setPosSelect(selected: Int) {
|
||||||
|
viewBinding.run {
|
||||||
|
for (i in 0 until parentLayout.childCount) {
|
||||||
|
val child = parentLayout.getChildAt(i)
|
||||||
|
if (child is RelativeLayout) {
|
||||||
|
if (i == selected) {
|
||||||
|
setItemSelect(child, true)
|
||||||
|
} else {
|
||||||
|
setItemSelect(child, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun show(anchor: View, xOff: Int = 0, yOff: Int = 0) {
|
||||||
|
showAsDropDown(anchor, xOff, yOff)
|
||||||
|
viewBinding.viewMask.apply {
|
||||||
|
alpha = 0f
|
||||||
|
animate().alpha(1f).setDuration(200).start()
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dismiss() {
|
||||||
|
Log.d("------------", "--------------dismiss")
|
||||||
|
viewBinding.viewMask.animate().alpha(0f).setDuration(200)
|
||||||
|
.withEndAction {
|
||||||
|
super.dismiss()
|
||||||
|
onClickDismiss.invoke()
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,26 +1,41 @@
|
|||||||
package com.ux.video.file.filerecovery.photo
|
package com.ux.video.file.filerecovery.photo
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.drawable.toDrawable
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
|
import androidx.core.graphics.toColorInt
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.ux.video.file.filerecovery.R
|
import com.ux.video.file.filerecovery.R
|
||||||
import com.ux.video.file.filerecovery.databinding.CommonLayoutSortItemBinding
|
import com.ux.video.file.filerecovery.databinding.DialogFilterCustomerDateBinding
|
||||||
import com.ux.video.file.filerecovery.databinding.DialogSortBinding
|
import com.ux.video.file.filerecovery.utils.Common
|
||||||
import com.google.android.material.datepicker.MaterialDatePicker
|
import com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
import com.google.android.material.datepicker.CalendarConstraints
|
import org.jaaksi.pickerview.picker.TimePicker
|
||||||
import com.google.android.material.datepicker.DateValidatorPointForward
|
import org.jaaksi.pickerview.picker.TimePicker.Companion.TYPE_DATE
|
||||||
import java.text.SimpleDateFormat
|
import org.jaaksi.pickerview.picker.TimePicker.OnTimeSelectListener
|
||||||
import java.util.*
|
import org.jaaksi.pickerview.widget.DefaultCenterDecoration
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
class DatePickerDialogFragment(val onClickSort: (type: Int) -> Unit) : DialogFragment() {
|
class DatePickerDialogFragment(
|
||||||
|
var mContext: Context,
|
||||||
private lateinit var binding: DialogSortBinding
|
var title: String,
|
||||||
|
var onPickerChooseListener: (selectedDate: Date)-> Unit,
|
||||||
|
var onClickCancel:()-> Unit
|
||||||
|
) :
|
||||||
|
DialogFragment(), OnTimeSelectListener {
|
||||||
|
|
||||||
|
private lateinit var binding: DialogFilterCustomerDateBinding
|
||||||
|
private lateinit var timerPicker: TimePicker
|
||||||
|
private var defaultSelectedDate = System.currentTimeMillis()
|
||||||
|
private var defaultStartDate = 1540361760000L
|
||||||
|
private var defaultEndDate = System.currentTimeMillis()
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
dialog?.window?.apply {
|
dialog?.window?.apply {
|
||||||
@ -28,7 +43,7 @@ class DatePickerDialogFragment(val onClickSort: (type: Int) -> Unit) : DialogFra
|
|||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
)
|
)
|
||||||
setGravity(Gravity.BOTTOM)
|
setGravity(Gravity.CENTER)
|
||||||
setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
|
setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,10 +52,114 @@ class DatePickerDialogFragment(val onClickSort: (type: Int) -> Unit) : DialogFra
|
|||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
binding = DialogSortBinding.inflate(inflater)
|
binding = DialogFilterCustomerDateBinding.inflate(inflater)
|
||||||
|
initDatePickerView()
|
||||||
|
binding.run {
|
||||||
|
textTitle.text =title
|
||||||
|
tvCancel.setOnClickListener {
|
||||||
|
if (!timerPicker.canSelected()) return@setOnClickListener
|
||||||
|
dismiss()
|
||||||
|
onClickCancel.invoke()
|
||||||
|
}
|
||||||
|
tvOk.setOnClickListener {
|
||||||
|
if (!timerPicker.canSelected()) return@setOnClickListener
|
||||||
|
dismiss()
|
||||||
|
timerPicker.onConfirm()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun setCurrentSelectedDate(dateTimer: Date){
|
||||||
|
defaultSelectedDate = dateTimer.time
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRangeDate(start: Date){
|
||||||
|
defaultStartDate = start.time
|
||||||
|
// defaultEndDate = end
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initDatePickerView() {
|
||||||
|
var index = 0
|
||||||
|
var centerDrawable: Drawable? = null
|
||||||
|
timerPicker = TimePicker.Builder(requireContext(), TYPE_DATE, this)
|
||||||
|
.setRangDate(defaultStartDate, defaultEndDate)
|
||||||
|
// .setContainsStarDate(true)
|
||||||
|
//.setTimeMinuteOffset(10)
|
||||||
|
// 设置选中时间
|
||||||
|
.setSelectedDate(defaultSelectedDate)
|
||||||
|
.setInterceptor { pickerView, params ->
|
||||||
|
pickerView.run {
|
||||||
|
visibleItemCount = 5
|
||||||
|
setTextSize(15, 19)
|
||||||
|
setFont(CustomTextView.bold)
|
||||||
|
setColor(
|
||||||
|
Common.getColorInt(requireContext(), R.color.main_title),
|
||||||
|
Common.getColorInt(requireContext(), R.color.main_sub_title)
|
||||||
|
)
|
||||||
|
centerDrawable = when (index) {
|
||||||
|
0 -> ContextCompat.getDrawable(
|
||||||
|
mContext,
|
||||||
|
R.drawable.bg_rectangle_15787880_left_13
|
||||||
|
)
|
||||||
|
|
||||||
|
1 -> ContextCompat.getDrawable(
|
||||||
|
mContext,
|
||||||
|
R.drawable.bg_rectangle_15787880_center
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> ContextCompat.getDrawable(
|
||||||
|
mContext,
|
||||||
|
R.drawable.bg_rectangle_15787880_right_13
|
||||||
|
)
|
||||||
|
}
|
||||||
|
index = index + 1
|
||||||
|
val centerDecoration =
|
||||||
|
DefaultCenterDecoration(requireContext())
|
||||||
|
.setLineColor("#00000000".toColorInt())
|
||||||
|
//.setMargin(margin, -margin, margin, -margin)
|
||||||
|
.setLineWidth(0f)
|
||||||
|
.setDrawable(centerDrawable)
|
||||||
|
setCenterDecoration(centerDecoration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setFormatter(object : TimePicker.DefaultFormatter() {
|
||||||
|
// 自定义Formatter显示去年,今年,明年
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
override fun format(
|
||||||
|
picker: TimePicker,
|
||||||
|
type: Int,
|
||||||
|
position: Int,
|
||||||
|
value: Long
|
||||||
|
): CharSequence {
|
||||||
|
if (type == TimePicker.TYPE_YEAR) {
|
||||||
|
return String.format("%d", value)
|
||||||
|
} else if (type == TimePicker.TYPE_MONTH) {
|
||||||
|
return String.format("%d", value)
|
||||||
|
} else if (type == TimePicker.TYPE_DAY) {
|
||||||
|
return String.format("%d", value)
|
||||||
|
}
|
||||||
|
return super.format(picker, type, position, value)
|
||||||
|
}
|
||||||
|
}).create()
|
||||||
|
timerPicker.view().let {
|
||||||
|
(it.parent as? ViewGroup)?.removeView(it)
|
||||||
|
binding.pickerContainer.addView(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTimeSelect(
|
||||||
|
picker: TimePicker,
|
||||||
|
date: Date
|
||||||
|
) {
|
||||||
|
defaultSelectedDate = date.time
|
||||||
|
onPickerChooseListener(date)
|
||||||
|
Common.showLog("--------onTimeSelect=${Common.getChineseFormatDate(date)}")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,12 +12,13 @@ import android.widget.RelativeLayout
|
|||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
import com.ux.video.file.filerecovery.databinding.CommonLayoutFilterItemBinding
|
import com.ux.video.file.filerecovery.databinding.CommonLayoutFilterItemBinding
|
||||||
import com.ux.video.file.filerecovery.databinding.PopwindowsFilterBinding
|
import com.ux.video.file.filerecovery.databinding.PopwindowsFilterBinding
|
||||||
|
import com.ux.video.file.filerecovery.utils.Common
|
||||||
import com.ux.video.file.filerecovery.utils.Common.setItemSelect
|
import com.ux.video.file.filerecovery.utils.Common.setItemSelect
|
||||||
import com.ux.video.file.filerecovery.utils.CustomTextView
|
import com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
|
||||||
class FilterPopupWindows(
|
class FilterPopupWindows(
|
||||||
context: Context, list: Array<String>,
|
context: Context, list: Array<String>,
|
||||||
selectedPos: Int,
|
var selectedPos: Int,
|
||||||
onClickConfirm: (value: String) -> Unit,
|
onClickConfirm: (value: String) -> Unit,
|
||||||
var onClickDismiss: () -> Unit
|
var onClickDismiss: () -> Unit
|
||||||
) :
|
) :
|
||||||
@ -46,6 +47,12 @@ class FilterPopupWindows(
|
|||||||
}
|
}
|
||||||
|
|
||||||
setData(list, selectedPos, onClickConfirm)
|
setData(list, selectedPos, onClickConfirm)
|
||||||
|
Common.showLog("--------------FilterPopupWindows init")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateSelectPos(index: Int) {
|
||||||
|
selectedPos = index
|
||||||
|
setPosSelect(selectedPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setData(
|
private fun setData(
|
||||||
@ -83,21 +90,18 @@ class FilterPopupWindows(
|
|||||||
val child = parentLayout.getChildAt(i)
|
val child = parentLayout.getChildAt(i)
|
||||||
if (child is RelativeLayout) {
|
if (child is RelativeLayout) {
|
||||||
child.setOnClickListener {
|
child.setOnClickListener {
|
||||||
for (j in 0 until parentLayout.childCount) {
|
//回调选中的文字
|
||||||
val other = parentLayout.getChildAt(j)
|
|
||||||
if (other is RelativeLayout) {
|
|
||||||
setItemSelect(other, false)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setItemSelect(child, true)
|
|
||||||
for (j in 0 until child.childCount) {
|
for (j in 0 until child.childCount) {
|
||||||
val other = child.getChildAt(j)
|
val other = child.getChildAt(j)
|
||||||
if (other is CustomTextView) {
|
if (other is CustomTextView) {
|
||||||
|
other.text.toString().let {
|
||||||
|
setPosSelect(i)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
onClickConfirm.invoke(other.text.toString())
|
onClickConfirm.invoke(other.text.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dismiss()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,6 +109,21 @@ class FilterPopupWindows(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setPosSelect(selected: Int) {
|
||||||
|
viewBinding.run {
|
||||||
|
for (i in 0 until parentLayout.childCount) {
|
||||||
|
val child = parentLayout.getChildAt(i)
|
||||||
|
if (child is RelativeLayout) {
|
||||||
|
if (i == selected) {
|
||||||
|
setItemSelect(child, true)
|
||||||
|
} else {
|
||||||
|
setItemSelect(child, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun show(anchor: View, xOff: Int = 0, yOff: Int = 0) {
|
fun show(anchor: View, xOff: Int = 0, yOff: Int = 0) {
|
||||||
showAsDropDown(anchor, xOff, yOff)
|
showAsDropDown(anchor, xOff, yOff)
|
||||||
|
|||||||
@ -1,18 +1,13 @@
|
|||||||
package com.ux.video.file.filerecovery.photo
|
package com.ux.video.file.filerecovery.photo
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.activity.viewModels
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.datepicker.CalendarConstraints
|
|
||||||
import com.google.android.material.datepicker.DateValidatorPointForward
|
|
||||||
import com.google.android.material.datepicker.MaterialDatePicker
|
|
||||||
import com.ux.video.file.filerecovery.R
|
import com.ux.video.file.filerecovery.R
|
||||||
import com.ux.video.file.filerecovery.base.BaseActivity
|
import com.ux.video.file.filerecovery.base.BaseActivity
|
||||||
import com.ux.video.file.filerecovery.databinding.ActivityPhotoSortingBinding
|
import com.ux.video.file.filerecovery.databinding.ActivityPhotoSortingBinding
|
||||||
@ -22,20 +17,18 @@ import com.ux.video.file.filerecovery.utils.Common.setItemSelect
|
|||||||
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
|
import com.ux.video.file.filerecovery.utils.ExtendFunctions.dpToPx
|
||||||
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterBySize
|
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterBySize
|
||||||
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterBySizeList
|
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterBySizeList
|
||||||
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinMonths
|
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinDateRange
|
||||||
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinMonthsList
|
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinDateRangeList
|
||||||
|
//import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinMonths
|
||||||
|
//import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinMonthsList
|
||||||
import com.ux.video.file.filerecovery.utils.ExtendFunctions.getParcelableArrayListExtraCompat
|
import com.ux.video.file.filerecovery.utils.ExtendFunctions.getParcelableArrayListExtraCompat
|
||||||
import com.ux.video.file.filerecovery.utils.ExtendFunctions.mbToBytes
|
import com.ux.video.file.filerecovery.utils.ExtendFunctions.mbToBytes
|
||||||
import com.ux.video.file.filerecovery.utils.ExtendFunctions.resetItemDecorationOnce
|
|
||||||
import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration
|
import com.ux.video.file.filerecovery.utils.GridSpacingItemDecoration
|
||||||
import com.ux.video.file.filerecovery.utils.ScanManager
|
import com.ux.video.file.filerecovery.utils.ScanManager
|
||||||
import com.ux.video.file.filerecovery.utils.ScanManager.copySelectedFilesAsync
|
import com.ux.video.file.filerecovery.utils.ScanManager.copySelectedFilesAsync
|
||||||
import com.ux.video.file.filerecovery.utils.ScanManager.deleteFilesAsync
|
import com.ux.video.file.filerecovery.utils.ScanManager.deleteFilesAsync
|
||||||
import com.ux.video.file.filerecovery.utils.ScanRepository
|
import com.ux.video.file.filerecovery.utils.ScanRepository
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||||
|
|
||||||
@ -47,6 +40,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
|||||||
val FILTER_DATE_1 = 1
|
val FILTER_DATE_1 = 1
|
||||||
val FILTER_DATE_6 = 6
|
val FILTER_DATE_6 = 6
|
||||||
val FILTER_DATE_24 = 24
|
val FILTER_DATE_24 = 24
|
||||||
|
val FILTER_DATE_CUSTOMER = 0
|
||||||
|
|
||||||
val FILTER_SIZE_ALL = -1
|
val FILTER_SIZE_ALL = -1
|
||||||
val FILTER_SIZE_1 = 1
|
val FILTER_SIZE_1 = 1
|
||||||
@ -70,6 +64,9 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
|||||||
|
|
||||||
private var dialogDeleting: DeletingDialogFragment? = null
|
private var dialogDeleting: DeletingDialogFragment? = null
|
||||||
|
|
||||||
|
private var dialogCustomerDateStart: DatePickerDialogFragment? = null
|
||||||
|
private var dialogCustomerDateEnd: DatePickerDialogFragment? = null
|
||||||
|
|
||||||
|
|
||||||
//默认倒序排序
|
//默认倒序排序
|
||||||
private var sortReverse = true
|
private var sortReverse = true
|
||||||
@ -80,7 +77,10 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
|||||||
//筛选大小,默认全部-1
|
//筛选大小,默认全部-1
|
||||||
private var filterSize = FILTER_SIZE_ALL
|
private var filterSize = FILTER_SIZE_ALL
|
||||||
|
|
||||||
private var filterDatePopupWindows: FilterPopupWindows? = null
|
private var filterDatePopupWindows: DateFilterPopupWindows? = null
|
||||||
|
private var filterStartDate: Date? = null
|
||||||
|
private var filterEndDate: Date? = null
|
||||||
|
|
||||||
private var filterSizePopupWindows: FilterPopupWindows? = null
|
private var filterSizePopupWindows: FilterPopupWindows? = null
|
||||||
private var filterLayoutPopupWindows: FilterPopupWindows? = null
|
private var filterLayoutPopupWindows: FilterPopupWindows? = null
|
||||||
|
|
||||||
@ -282,9 +282,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
|||||||
val bPx = 6.dpToPx(context)
|
val bPx = 6.dpToPx(context)
|
||||||
setPadding(aPx, 0, bPx, 0)
|
setPadding(aPx, 0, bPx, 0)
|
||||||
clipToPadding = false
|
clipToPadding = false
|
||||||
|
|
||||||
layoutManager = GridLayoutManager(context, columns)
|
layoutManager = GridLayoutManager(context, columns)
|
||||||
// resetItemDecorationOnce(mItemDecoration)
|
|
||||||
adapter = sizeSortAdapter
|
adapter = sizeSortAdapter
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -299,20 +297,52 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
|||||||
filterDateLayout.setOnClickListener {
|
filterDateLayout.setOnClickListener {
|
||||||
setItemSelect(it as LinearLayout, true)
|
setItemSelect(it as LinearLayout, true)
|
||||||
resources.getStringArray(R.array.filter_date).let { data ->
|
resources.getStringArray(R.array.filter_date).let { data ->
|
||||||
filterDatePopupWindows = filterDatePopupWindows ?: FilterPopupWindows(
|
filterDatePopupWindows = filterDatePopupWindows ?: DateFilterPopupWindows(
|
||||||
this@PhotoSortingActivity,
|
this@PhotoSortingActivity,
|
||||||
data,
|
|
||||||
0,
|
0,
|
||||||
{ clickValue ->
|
{ clickValue, showDialog ->
|
||||||
when (clickValue) {
|
when (clickValue) {
|
||||||
data[0] -> filterDate = FILTER_DATE_ALL
|
data[0] -> {
|
||||||
data[1] -> filterDate = FILTER_DATE_1
|
filterDate = FILTER_DATE_ALL
|
||||||
data[2] -> filterDate = FILTER_DATE_6
|
|
||||||
data[3] -> filterDate = FILTER_DATE_24
|
|
||||||
data[4] -> showDatePicker()
|
|
||||||
}
|
|
||||||
startFilter()
|
startFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
data[1] -> {
|
||||||
|
filterDate = FILTER_DATE_1
|
||||||
|
startFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
data[2] -> {
|
||||||
|
filterDate = FILTER_DATE_6
|
||||||
|
startFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
data[3] -> {
|
||||||
|
filterDate = FILTER_DATE_24
|
||||||
|
startFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
data[4] -> {
|
||||||
|
filterDate = FILTER_DATE_CUSTOMER
|
||||||
|
if (showDialog)
|
||||||
|
showStartDateDialog(true,null)
|
||||||
|
else {
|
||||||
|
startFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, onResetDate = { isStart,currentDate ->
|
||||||
|
if (isStart) {
|
||||||
|
showStartDateDialog(false,currentDate)
|
||||||
|
} else {
|
||||||
|
showEndDateDialog(false,currentDate)
|
||||||
|
}
|
||||||
|
|
||||||
}) {
|
}) {
|
||||||
|
//dismiss
|
||||||
|
|
||||||
setItemSelect(it, false)
|
setItemSelect(it, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,13 +438,18 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
|||||||
* 执行筛选结果
|
* 执行筛选结果
|
||||||
*/
|
*/
|
||||||
private fun startFilter() {
|
private fun startFilter() {
|
||||||
|
Common.showLog("--------------开始筛选")
|
||||||
when (binding.recyclerView.adapter) {
|
when (binding.recyclerView.adapter) {
|
||||||
//当前是时间排序
|
//当前是时间排序
|
||||||
is PhotoDisplayDateAdapter -> {
|
is PhotoDisplayDateAdapter -> {
|
||||||
//确定当前排序
|
//确定当前排序
|
||||||
val list = if (sortReverse) sortByDateReverse else sortedByDatePositive
|
val list = if (sortReverse) sortByDateReverse else sortedByDatePositive
|
||||||
val filterSizeCovert = filterSizeCovert(filterSize)
|
val filterSizeCovert = filterSizeCovert(filterSize)
|
||||||
list.filterWithinMonths(filterDate)
|
list.filterWithinDateRange(
|
||||||
|
filterDate,
|
||||||
|
startDate = if (filterDate == FILTER_DATE_CUSTOMER) filterStartDate else null,
|
||||||
|
endDate = if (filterDate == FILTER_DATE_CUSTOMER) filterEndDate else null
|
||||||
|
)
|
||||||
.filterBySize(filterSizeCovert.first, filterSizeCovert.second)
|
.filterBySize(filterSizeCovert.first, filterSizeCovert.second)
|
||||||
.let { currentList ->
|
.let { currentList ->
|
||||||
//对筛选后的数据与实际选中集合对比 ,得出筛选后显示的数据中的选中数据
|
//对筛选后的数据与实际选中集合对比 ,得出筛选后显示的数据中的选中数据
|
||||||
@ -434,7 +469,11 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
|||||||
is PhotoDisplayDateChildAdapter -> {
|
is PhotoDisplayDateChildAdapter -> {
|
||||||
val list = if (sortReverse) sortBySizeBigToSmall else sortBySizeSmallToBig
|
val list = if (sortReverse) sortBySizeBigToSmall else sortBySizeSmallToBig
|
||||||
val filterSizeCovert = filterSizeCovert(filterSize)
|
val filterSizeCovert = filterSizeCovert(filterSize)
|
||||||
list.filterWithinMonthsList(filterDate)
|
list.filterWithinDateRangeList(
|
||||||
|
filterDate,
|
||||||
|
startDate = if (filterDate == FILTER_DATE_CUSTOMER) filterStartDate else null,
|
||||||
|
endDate = if (filterDate == FILTER_DATE_CUSTOMER) filterEndDate else null
|
||||||
|
)
|
||||||
.filterBySizeList(filterSizeCovert.first, filterSizeCovert.second)
|
.filterBySizeList(filterSizeCovert.first, filterSizeCovert.second)
|
||||||
.let { currentList ->
|
.let { currentList ->
|
||||||
//对筛选后的数据与实际选中集合对比 ,得出筛选后显示的数据中的选中数据
|
//对筛选后的数据与实际选中集合对比 ,得出筛选后显示的数据中的选中数据
|
||||||
@ -461,33 +500,75 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun showDatePicker() {
|
private fun showStartDateDialog(isNeedSetSelected: Boolean,currentDate: Date?) {
|
||||||
// 创建日期选择器构建器
|
|
||||||
val builder = MaterialDatePicker.Builder.datePicker()
|
|
||||||
builder.setTitleText("选择日期")
|
|
||||||
|
|
||||||
// 可选:限制可选日期,比如只能选择今天之后的日期
|
dialogCustomerDateStart = dialogCustomerDateStart ?: DatePickerDialogFragment(
|
||||||
val constraintsBuilder = CalendarConstraints.Builder()
|
this,
|
||||||
constraintsBuilder.setValidator(DateValidatorPointForward.now()) // 今天之后
|
getString(R.string.start_date),
|
||||||
builder.setCalendarConstraints(constraintsBuilder.build())
|
onPickerChooseListener = {}, onClickCancel = {})
|
||||||
|
dialogCustomerDateStart?.run {
|
||||||
|
currentDate?.let {
|
||||||
|
setCurrentSelectedDate(it)
|
||||||
|
}
|
||||||
|
onPickerChooseListener = { selectedDate ->
|
||||||
|
filterStartDate = selectedDate
|
||||||
|
// filterDatePopupWindows?.updateStartEndDate(start = selectedDate)
|
||||||
|
Log.d("showStartDateDialog", "isFirst=${isNeedSetSelected}--------")
|
||||||
|
showEndDateDialog(isNeedSetSelected,null)
|
||||||
|
|
||||||
val datePicker = builder.build()
|
// if (isFirst) {
|
||||||
|
// showEndDateDialog(true)
|
||||||
|
// } else {
|
||||||
|
// if (filterDate == FILTER_DATE_CUSTOMER) {
|
||||||
|
// startFilter()
|
||||||
|
// filterDatePopupWindows?.dismiss()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
onClickCancel = {
|
||||||
|
|
||||||
// 显示日期选择器
|
}
|
||||||
datePicker.show(supportFragmentManager, "MATERIAL_DATE_PICKER")
|
show(supportFragmentManager, "")
|
||||||
|
|
||||||
// 监听用户选择
|
}
|
||||||
datePicker.addOnPositiveButtonClickListener { selection ->
|
|
||||||
// selection 是 Long 类型的时间戳(UTC 毫秒)
|
}
|
||||||
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
|
||||||
val dateString = sdf.format(Date(selection))
|
private fun showEndDateDialog(isNeedSetSelected: Boolean,currentDate: Date?) {
|
||||||
println("用户选择的日期:$dateString")
|
dialogCustomerDateEnd = dialogCustomerDateEnd ?: DatePickerDialogFragment(
|
||||||
|
this,
|
||||||
|
getString(R.string.end_date),
|
||||||
|
onPickerChooseListener = {}, onClickCancel = {})
|
||||||
|
dialogCustomerDateEnd?.run {
|
||||||
|
currentDate?.let {
|
||||||
|
setCurrentSelectedDate(it)
|
||||||
|
}
|
||||||
|
filterStartDate?.let { setRangeDate(it) }
|
||||||
|
onPickerChooseListener = { selectedDate ->
|
||||||
|
filterEndDate = selectedDate
|
||||||
|
if(isNeedSetSelected){
|
||||||
|
filterDatePopupWindows?.updateSelectPos(4, filterStartDate!!, filterEndDate!!)
|
||||||
|
startFilter()
|
||||||
|
filterDatePopupWindows?.dismiss()
|
||||||
|
}else{
|
||||||
|
filterDatePopupWindows?.updateStartEndDate(filterStartDate,filterEndDate)
|
||||||
|
if (filterDate == FILTER_DATE_CUSTOMER) {
|
||||||
|
startFilter()
|
||||||
|
filterDatePopupWindows?.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
onClickCancel = {
|
||||||
|
// filterDatePopupWindows?.dismiss()
|
||||||
|
}
|
||||||
|
show(supportFragmentManager, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun showRecoveringDialog() {
|
private fun showRecoveringDialog() {
|
||||||
dialogRecovering = dialogRecovering ?: RecoveringDialogFragment(filterSelectedSetList.size){
|
dialogRecovering =
|
||||||
|
dialogRecovering ?: RecoveringDialogFragment(filterSelectedSetList.size) {
|
||||||
complete()
|
complete()
|
||||||
}
|
}
|
||||||
dialogRecovering?.show(supportFragmentManager, "")
|
dialogRecovering?.show(supportFragmentManager, "")
|
||||||
@ -500,6 +581,9 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
|||||||
dialogDeleting?.show(supportFragmentManager, "")
|
dialogDeleting?.show(supportFragmentManager, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除或者恢复完成
|
||||||
|
*/
|
||||||
private fun complete() {
|
private fun complete() {
|
||||||
dialogDeleting?.dismiss()
|
dialogDeleting?.dismiss()
|
||||||
dialogRecovering?.dismiss()
|
dialogRecovering?.dismiss()
|
||||||
|
|||||||
@ -5,7 +5,9 @@ import android.icu.text.SimpleDateFormat
|
|||||||
import android.icu.util.Calendar
|
import android.icu.util.Calendar
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.ux.video.file.filerecovery.App
|
import com.ux.video.file.filerecovery.App
|
||||||
import com.ux.video.file.filerecovery.R
|
import com.ux.video.file.filerecovery.R
|
||||||
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
|
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
|
||||||
@ -32,6 +34,7 @@ object Common {
|
|||||||
|
|
||||||
val rootDir = Environment.getExternalStorageDirectory()
|
val rootDir = Environment.getExternalStorageDirectory()
|
||||||
val dateFormat = SimpleDateFormat("MMMM d,yyyy", Locale.ENGLISH)
|
val dateFormat = SimpleDateFormat("MMMM d,yyyy", Locale.ENGLISH)
|
||||||
|
val chineseFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINESE)
|
||||||
val recoveryPhotoDir = "MyAllRecovery/Photo"
|
val recoveryPhotoDir = "MyAllRecovery/Photo"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -127,14 +130,21 @@ object Common {
|
|||||||
/**
|
/**
|
||||||
* 设置全部子View的选中
|
* 设置全部子View的选中
|
||||||
*/
|
*/
|
||||||
fun setItemSelect(view: ViewGroup, boolean: Boolean) {
|
// fun setItemSelect(view: ViewGroup, boolean: Boolean) {
|
||||||
|
// for (i in 0 until view.childCount) {
|
||||||
|
// val child = view.getChildAt(i)
|
||||||
|
// child.isSelected = boolean
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
fun setItemSelect(view: View, selected: Boolean) {
|
||||||
|
view.isSelected = selected
|
||||||
|
if (view is ViewGroup) {
|
||||||
for (i in 0 until view.childCount) {
|
for (i in 0 until view.childCount) {
|
||||||
val child = view.getChildAt(i)
|
val child = view.getChildAt(i)
|
||||||
child.isSelected = boolean
|
setItemSelect(child, selected)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -161,7 +171,10 @@ object Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun checkSelectListContainSize(list: List<ResultPhotosFiles>, selected: Set<String>): Pair<Int, MutableSet<String>> {
|
fun checkSelectListContainSize(
|
||||||
|
list: List<ResultPhotosFiles>,
|
||||||
|
selected: Set<String>
|
||||||
|
): Pair<Int, MutableSet<String>> {
|
||||||
val currentSelected = mutableSetOf<String>()
|
val currentSelected = mutableSetOf<String>()
|
||||||
val totalSelectedCount = list.count {
|
val totalSelectedCount = list.count {
|
||||||
val isSelected = it.path in selected
|
val isSelected = it.path in selected
|
||||||
@ -176,6 +189,19 @@ object Common {
|
|||||||
return dateFormat.format(Date(time))
|
return dateFormat.format(Date(time))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getChineseFormatDate(date: Date): String {
|
||||||
|
return chineseFormat.format(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDateFromFormat(dateStr: String): Date? {
|
||||||
|
return chineseFormat.parse(dateStr)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getColorInt(context: Context, colorId: Int): Int {
|
||||||
|
return ContextCompat.getColor(context, colorId)
|
||||||
|
}
|
||||||
|
|
||||||
fun showLog(msg: String) {
|
fun showLog(msg: String) {
|
||||||
Log.d("============", msg)
|
Log.d("============", msg)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ class CustomTextView @JvmOverloads constructor(
|
|||||||
) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyleAttr) {
|
) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private var regular: Typeface? = null
|
private var regular: Typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
|
||||||
private var bold: Typeface? = null
|
var bold: Typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
|
||||||
private var alimama: Typeface? = null
|
private var alimama: Typeface? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,25 +27,20 @@ class CustomTextView @JvmOverloads constructor(
|
|||||||
selectedText = typedArray.getString(R.styleable.CustomTextView_selected_text)
|
selectedText = typedArray.getString(R.styleable.CustomTextView_selected_text)
|
||||||
normalText = typedArray.getString(R.styleable.CustomTextView_normal_text)
|
normalText = typedArray.getString(R.styleable.CustomTextView_normal_text)
|
||||||
typedArray.recycle()
|
typedArray.recycle()
|
||||||
|
if (alimama == null) {
|
||||||
Typeface.create("sans-serif-light", Typeface.NORMAL) // Roboto Light
|
alimama = Typeface.createFromAsset(context.assets, "fonts/alimama.ttf")
|
||||||
Typeface.create("sans-serif-thin", Typeface.ITALIC) // Roboto Thin Italic
|
}
|
||||||
Typeface.create("sans-serif-medium", Typeface.BOLD) // Roboto Medium Bold
|
|
||||||
Typeface.create("sans-serif-condensed", Typeface.NORMAL) // Roboto Condensed
|
|
||||||
when (type) {
|
when (type) {
|
||||||
0 -> {
|
0 -> {
|
||||||
typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
|
typeface = regular
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
|
|
||||||
typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
|
typeface = bold
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> {
|
2 -> {
|
||||||
if (alimama == null) {
|
|
||||||
alimama = Typeface.createFromAsset(context.assets, "fonts/alimama.ttf")
|
|
||||||
}
|
|
||||||
typeface = alimama
|
typeface = alimama
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import android.os.Parcelable
|
|||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
|
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
object ExtendFunctions {
|
object ExtendFunctions {
|
||||||
|
|
||||||
@ -40,17 +41,57 @@ object ExtendFunctions {
|
|||||||
/**
|
/**
|
||||||
* 按时间筛选:最近 N 个月
|
* 按时间筛选:最近 N 个月
|
||||||
*/
|
*/
|
||||||
fun List<ResultPhotosFiles>.filterWithinMonthsList(months: Int): List<ResultPhotosFiles> {
|
// fun List<ResultPhotosFiles>.filterWithinMonthsList(months: Int): List<ResultPhotosFiles> {
|
||||||
if (months == -1) return this
|
// if (months == -1) return this
|
||||||
|
// val today = Calendar.getInstance()
|
||||||
|
// val monthsAgo = Calendar.getInstance().apply {
|
||||||
|
// add(Calendar.MONTH, -months)
|
||||||
|
// }
|
||||||
|
// return this.filter {
|
||||||
|
// val cal = Calendar.getInstance().apply { timeInMillis = it.lastModified }
|
||||||
|
// !cal.before(monthsAgo) && !cal.after(today)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fun List<ResultPhotosFiles>.filterWithinDateRangeList(
|
||||||
|
months: Int = -1,
|
||||||
|
startDate: Date? = null,
|
||||||
|
endDate: Date? = null
|
||||||
|
): List<ResultPhotosFiles> {
|
||||||
|
|
||||||
val today = Calendar.getInstance()
|
val today = Calendar.getInstance()
|
||||||
|
|
||||||
|
return when {
|
||||||
|
// ✅ 1. -1 表示不过滤,返回全部
|
||||||
|
months == -1 -> this
|
||||||
|
|
||||||
|
// ✅ 2. 0 表示仅根据 startDate / endDate 筛选
|
||||||
|
months == 0 -> this.filter { file ->
|
||||||
|
val date = Date(file.lastModified)
|
||||||
|
when {
|
||||||
|
startDate != null && endDate != null -> date in startDate..endDate
|
||||||
|
// startDate != null -> !date.before(startDate)
|
||||||
|
// endDate != null -> !date.after(endDate)
|
||||||
|
else -> true // 没传日期则默认不过滤
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 3. 其他情况:按“最近 N 个月”筛选
|
||||||
|
else -> {
|
||||||
val monthsAgo = Calendar.getInstance().apply {
|
val monthsAgo = Calendar.getInstance().apply {
|
||||||
add(Calendar.MONTH, -months)
|
add(Calendar.MONTH, -months)
|
||||||
}
|
}
|
||||||
return this.filter {
|
|
||||||
val cal = Calendar.getInstance().apply { timeInMillis = it.lastModified }
|
this.filter { file ->
|
||||||
|
val cal = Calendar.getInstance().apply { timeInMillis = file.lastModified }
|
||||||
!cal.before(monthsAgo) && !cal.after(today)
|
!cal.before(monthsAgo) && !cal.after(today)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按文件大小筛选:区间 [minSize, maxSize]
|
* 按文件大小筛选:区间 [minSize, maxSize]
|
||||||
@ -66,19 +107,61 @@ object ExtendFunctions {
|
|||||||
/**
|
/**
|
||||||
* 按时间筛选:最近 N 个月
|
* 按时间筛选:最近 N 个月
|
||||||
*/
|
*/
|
||||||
fun List<Pair<String, List<ResultPhotosFiles>>>.filterWithinMonths(months: Int): List<Pair<String, List<ResultPhotosFiles>>> {
|
// fun List<Pair<String, List<ResultPhotosFiles>>>.filterWithinMonths(months: Int): List<Pair<String, List<ResultPhotosFiles>>> {
|
||||||
if (months == -1) return this
|
// if (months == -1) return this
|
||||||
|
// val sdf = Common.dateFormat
|
||||||
|
// val today = Calendar.getInstance()
|
||||||
|
// val monthsAgo = Calendar.getInstance().apply {
|
||||||
|
// add(Calendar.MONTH, -months)
|
||||||
|
// }
|
||||||
|
// return this.filter { (dayStr, _) ->
|
||||||
|
// val day = sdf.parse(dayStr)
|
||||||
|
// day != null && !day.before(monthsAgo.time) && !day.after(today.time)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
fun List<Pair<String, List<ResultPhotosFiles>>>.filterWithinDateRange(
|
||||||
|
months: Int = -1,
|
||||||
|
startDate: Date? = null,
|
||||||
|
endDate: Date? = null
|
||||||
|
): List<Pair<String, List<ResultPhotosFiles>>> {
|
||||||
|
|
||||||
val sdf = Common.dateFormat
|
val sdf = Common.dateFormat
|
||||||
val today = Calendar.getInstance()
|
val today = Calendar.getInstance()
|
||||||
|
|
||||||
|
// 计算“几个月前”的时间
|
||||||
val monthsAgo = Calendar.getInstance().apply {
|
val monthsAgo = Calendar.getInstance().apply {
|
||||||
add(Calendar.MONTH, -months)
|
add(Calendar.MONTH, -months)
|
||||||
}
|
}
|
||||||
return this.filter { (dayStr, _) ->
|
|
||||||
val day = sdf.parse(dayStr)
|
return when {
|
||||||
day != null && !day.before(monthsAgo.time) && !day.after(today.time)
|
// -1 表示不过滤,返回全部
|
||||||
|
months == -1 -> this
|
||||||
|
|
||||||
|
// 0 表示只用日期范围过滤
|
||||||
|
months == 0 -> this.filter { (dayStr, _) ->
|
||||||
|
val day = sdf.parse(dayStr) ?: return@filter false
|
||||||
|
when {
|
||||||
|
startDate != null && endDate != null -> day in startDate..endDate
|
||||||
|
// startDate != null -> !day.before(startDate)
|
||||||
|
// endDate != null -> !day.after(endDate)
|
||||||
|
else -> true // 没传 start/end 则默认不过滤
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 其他情况:按“最近 N 个月”过滤
|
||||||
|
else -> this.filter { (dayStr, _) ->
|
||||||
|
val day = sdf.parse(dayStr) ?: return@filter false
|
||||||
|
!day.before(monthsAgo.time) && !day.after(today.time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分组数据:按大小筛选
|
* 分组数据:按大小筛选
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -41,7 +41,7 @@ class ScanRepository : ViewModel() {
|
|||||||
_selectedLiveData.value = current.toSet()
|
_selectedLiveData.value = current.toSet()
|
||||||
_selectedDisplayLiveData.value = currentDisplay.toSet()
|
_selectedDisplayLiveData.value = currentDisplay.toSet()
|
||||||
|
|
||||||
Log.d("CallTrace", Log.getStackTraceString(Exception("Call trace")))
|
// Log.d("CallTrace", Log.getStackTraceString(Exception("Call trace")))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="8dp"/>
|
||||||
|
<solid android:color="@color/switch_track_true"/>
|
||||||
|
|
||||||
|
</shape>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="8dp"/>
|
||||||
|
<solid android:color="@color/date_dialog_bg_unselected"/>
|
||||||
|
|
||||||
|
</shape>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<solid android:color="@color/date_dialog_center_bg" />
|
||||||
|
|
||||||
|
</shape>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:topLeftRadius="13dp" android:bottomLeftRadius="13dp" />
|
||||||
|
<solid android:color="@color/date_dialog_center_bg" />
|
||||||
|
|
||||||
|
</shape>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:topRightRadius="13dp" android:bottomRightRadius="13dp" />
|
||||||
|
<solid android:color="@color/date_dialog_center_bg" />
|
||||||
|
|
||||||
|
</shape>
|
||||||
5
app/src/main/res/drawable/selector_filter_date.xml
Normal file
5
app/src/main/res/drawable/selector_filter_date.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/bg_dialog_filter_date_start_selected_d5ebff_8" android:state_selected="true" />
|
||||||
|
<item android:drawable="@drawable/bg_dialog_filter_date_start_unselected_f2f2f7_8" android:state_selected="false" />
|
||||||
|
</selector>
|
||||||
@ -247,6 +247,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/layout_wchat">
|
app:layout_constraintTop_toBottomOf="@id/layout_wchat">
|
||||||
|
|
||||||
|
|||||||
109
app/src/main/res/layout/dialog_filter_customer_date_.xml
Normal file
109
app/src/main/res/layout/dialog_filter_customer_date_.xml
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<FrameLayout 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="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:background="@drawable/bg_rectangle_white_20"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:paddingVertical="30dp">
|
||||||
|
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/start_date"
|
||||||
|
android:id="@+id/text_title"
|
||||||
|
android:textColor="@color/main_title"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:fontType="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Year"
|
||||||
|
android:textColor="@color/main_title"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:fontType="bold" />
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Month"
|
||||||
|
android:textColor="@color/main_title"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:fontType="bold" />
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Day"
|
||||||
|
android:textColor="@color/main_title"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:fontType="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/pickerContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:layout_height="150dp" />
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginTop="26dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:id="@+id/tv_cancel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_dialog_btn_cancel_stoke_8"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/cancel"
|
||||||
|
android:textColor="@color/main_text_blue"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:fontType="bold" />
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:id="@+id/tv_ok"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_dialog_btn_allow_solid_8"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/ok"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:fontType="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
203
app/src/main/res/layout/popwindows_filter_date.xml
Normal file
203
app/src/main/res/layout/popwindows_filter_date.xml
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<FrameLayout 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">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/view_mask"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:alpha="0"
|
||||||
|
android:background="@color/popup_windows_mask"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/parent_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_rectangle_white_bottom_20"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/layout_all"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:paddingHorizontal="16dp">
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:id="@+id/tv_all"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:textColor="@color/selector_c7c7cc_blue"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:fontType="bold"
|
||||||
|
tools:ignore="RelativeOverlap"
|
||||||
|
tools:text="@string/allow" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:src="@drawable/selector_filter_image_view" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/layout_1_month"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:paddingHorizontal="16dp">
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:id="@+id/tv_1"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:textColor="@color/selector_c7c7cc_blue"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:fontType="bold"
|
||||||
|
tools:ignore="RelativeOverlap"
|
||||||
|
tools:text="@string/allow" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:src="@drawable/selector_filter_image_view" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/layout_6_month"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:paddingHorizontal="16dp">
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:id="@+id/tv_6"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:textColor="@color/selector_c7c7cc_blue"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:fontType="bold"
|
||||||
|
tools:ignore="RelativeOverlap"
|
||||||
|
tools:text="@string/allow" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:src="@drawable/selector_filter_image_view" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/layout_24_month"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:paddingHorizontal="16dp">
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:id="@+id/tv_24"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:textColor="@color/selector_c7c7cc_blue"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:fontType="bold"
|
||||||
|
tools:ignore="RelativeOverlap"
|
||||||
|
tools:text="@string/allow" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:src="@drawable/selector_filter_image_view" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/layout_customize"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="16dp">
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:id="@+id/tv_customize"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@color/selector_c7c7cc_blue"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:fontType="bold"
|
||||||
|
tools:ignore="RelativeOverlap"
|
||||||
|
tools:text="@string/allow" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
android:layout_alignTop="@id/tv_customize"
|
||||||
|
android:layout_alignBottom="@id/tv_customize"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:src="@drawable/selector_filter_image_view"
|
||||||
|
tools:src="@drawable/icon_checkmark_filter" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_start_end"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_below="@id/tv_customize"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingBottom="14dp">
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:id="@+id/tv_start_date"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/selector_filter_date"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:paddingVertical="6dp"
|
||||||
|
android:textColor="@color/selector_c7c7cc_blue"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:fontType="bold"
|
||||||
|
tools:ignore="RelativeOverlap"
|
||||||
|
tools:text="@string/allow" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginHorizontal="11dp"
|
||||||
|
android:background="@color/view_div_color" />
|
||||||
|
|
||||||
|
<com.ux.video.file.filerecovery.utils.CustomTextView
|
||||||
|
android:id="@+id/tv_end_date"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/selector_filter_date"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:paddingVertical="6dp"
|
||||||
|
android:textColor="@color/selector_c7c7cc_blue"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:fontType="bold"
|
||||||
|
tools:ignore="RelativeOverlap"
|
||||||
|
tools:text="@string/allow" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@ -21,5 +21,9 @@
|
|||||||
<color name="dialog_shape">#0048FD</color>
|
<color name="dialog_shape">#0048FD</color>
|
||||||
<color name="dialog_shape_delete">#fdad00</color>
|
<color name="dialog_shape_delete">#fdad00</color>
|
||||||
<color name="dialog_progress_color">#6BFFFFFF</color>
|
<color name="dialog_progress_color">#6BFFFFFF</color>
|
||||||
|
<color name="date_dialog_center_bg">#15787880</color>
|
||||||
|
<color name="date_dialog_bg_unselected">#F2F2F7</color>
|
||||||
|
<color name="view_div_color">#D9D9D9</color>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@ -73,6 +73,8 @@ wait..</string>
|
|||||||
<string name="recovered_success">Recovered successfully!</string>
|
<string name="recovered_success">Recovered successfully!</string>
|
||||||
<string name="deleted_success">Deleted successfully!</string>
|
<string name="deleted_success">Deleted successfully!</string>
|
||||||
<string name="text_continue">Continue</string>
|
<string name="text_continue">Continue</string>
|
||||||
|
<string name="start_date">Start date</string>
|
||||||
|
<string name="end_date">End date</string>
|
||||||
|
|
||||||
<string-array name="filter_date">
|
<string-array name="filter_date">
|
||||||
<item>All</item>
|
<item>All</item>
|
||||||
|
|||||||
1
pickerview/.gitignore
vendored
Normal file
1
pickerview/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
59
pickerview/build.gradle
Normal file
59
pickerview/build.gradle
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
//apply plugin: 'com.novoda.bintray-release'
|
||||||
|
//apply from: 'publish.gradle'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 36
|
||||||
|
namespace = "org.jaaksi.pickerview"
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 15
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
checkReleaseBuilds false
|
||||||
|
// Or, if you prefer, you can continue to check for errors in release builds,
|
||||||
|
// but continue the build even when errors are found:
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果你开源库中有中文注释在根目录的build.gradle中的all加入格式
|
||||||
|
tasks.withType(Javadoc) {
|
||||||
|
options {
|
||||||
|
encoding "UTF-8"
|
||||||
|
charSet 'UTF-8'
|
||||||
|
links "http://docs.oracle.com/javase/7/docs/api"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解决Javadoc 出错 Javadoc generation failed. Generated Javadoc options file (useful for troubleshooting):
|
||||||
|
options.addStringOption('Xdoclint:none', '-quiet')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "androidx.core:core-ktx:1.6.0"
|
||||||
|
// implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||||
|
implementation 'com.google.android.material:material:1.6.1'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
}
|
||||||
21
pickerview/proguard-rules.pro
vendored
Normal file
21
pickerview/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# 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
|
||||||
72
pickerview/publish.gradle
Normal file
72
pickerview/publish.gradle
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
def getLocalProperties(String key, Object defValue) {
|
||||||
|
try {
|
||||||
|
Properties properties = new Properties()
|
||||||
|
properties.load(new File(rootDir.absolutePath + "/local.properties").newDataInputStream())
|
||||||
|
def value = properties.getProperty(key, defValue)
|
||||||
|
return value
|
||||||
|
} catch (Exception e) {
|
||||||
|
return defValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def bintrayUser = getLocalProperties("bintrayUser", "")
|
||||||
|
def bintrayKey = getLocalProperties("bintrayKey", "")
|
||||||
|
|
||||||
|
def LibVersion = '3.0.2'
|
||||||
|
|
||||||
|
publish {
|
||||||
|
userOrg = bintrayUser //bintray注册的用户名所属组织名
|
||||||
|
groupId = 'org.jaaksi' //compile引用时的第1部分groupId
|
||||||
|
artifactId = 'pickerview' //compile引用时的第2部分项目名
|
||||||
|
publishVersion = LibVersion //compile引用时的第3部分版本号
|
||||||
|
desc = 'This is a pickerView library.' //d项目描述
|
||||||
|
repoName = "maven" //你的仓库名称,没有填写默认仓库是maven
|
||||||
|
// website = 'https://github.com/jaaksi/maven.git' // 网站,最好有,不重要
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate { project ->
|
||||||
|
task sourcesJar(type: Jar) {
|
||||||
|
classifier = 'sources'
|
||||||
|
from android.sourceSets.main.java.sourceFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
/*task javadoc(type: Javadoc) {
|
||||||
|
source = android.sourceSets.main.java.srcDirs
|
||||||
|
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
|
||||||
|
}
|
||||||
|
|
||||||
|
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||||
|
classifier = 'javadoc'
|
||||||
|
from javadoc.destinationDir
|
||||||
|
}
|
||||||
|
|
||||||
|
javadoc {
|
||||||
|
options {
|
||||||
|
encoding 'UTF-8'
|
||||||
|
charSet 'UTF-8'
|
||||||
|
author true
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
// archives javadocJar
|
||||||
|
archives sourcesJar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传到jcenter时需要pom文件,但是直接执行不会执行generatePomFile task,所以这里执行clean之后主动执行以下generatePomFile
|
||||||
|
|
||||||
|
task push {
|
||||||
|
doLast {
|
||||||
|
exec {
|
||||||
|
try {
|
||||||
|
executable 'bash'
|
||||||
|
args "-c",
|
||||||
|
"gradle clean generatePomFileForReleasePublication build bintrayUpload -PbintrayUser=$bintrayUser -PbintrayKey=$bintrayKey -PdryRun=false"
|
||||||
|
println commandType
|
||||||
|
} catch (Exception e) {
|
||||||
|
println e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
pickerview/src/main/AndroidManifest.xml
Normal file
2
pickerview/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.jaaksi.pickerview" />
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package org.jaaksi.pickerview.adapter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The simple Array wheel adapter
|
||||||
|
* 数据实现 [PickerDataSet]即可
|
||||||
|
*
|
||||||
|
* @param <T> the element type
|
||||||
|
*/
|
||||||
|
class ArrayWheelAdapter<T>(val data: List<T>?) : WheelAdapter<T> {
|
||||||
|
|
||||||
|
override fun getItem(index: Int): T? {
|
||||||
|
return data?.getOrNull(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val itemCount: Int
|
||||||
|
get() = data?.size ?: 0
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package org.jaaksi.pickerview.adapter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数字adapter
|
||||||
|
*
|
||||||
|
* @param minValue the wheel min value
|
||||||
|
* @param maxValue the wheel max value
|
||||||
|
*/
|
||||||
|
class NumericWheelAdapter(private val minValue: Int, private val maxValue: Int) :
|
||||||
|
WheelAdapter<Int> {
|
||||||
|
override val itemCount: Int
|
||||||
|
get() = maxValue - minValue + 1
|
||||||
|
|
||||||
|
override fun getItem(index: Int): Int {
|
||||||
|
return if (index in 0 until itemCount) minValue + index else 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package org.jaaksi.pickerview.adapter
|
||||||
|
|
||||||
|
interface WheelAdapter<T> {
|
||||||
|
/**
|
||||||
|
* Gets items count
|
||||||
|
*
|
||||||
|
* @return the count of wheel items
|
||||||
|
*/
|
||||||
|
val itemCount: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a wheel item by index.
|
||||||
|
*
|
||||||
|
* @param index the item index
|
||||||
|
* @return the wheel item text or null
|
||||||
|
*/
|
||||||
|
fun getItem(index: Int): T?
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package org.jaaksi.pickerview.dataset
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by fuchaoyang on 2018/2/11.<br></br>
|
||||||
|
* description:[OptionPicker]专用数据集
|
||||||
|
*/
|
||||||
|
interface OptionDataSet : PickerDataSet {
|
||||||
|
/**
|
||||||
|
* @return 下一级的数据集
|
||||||
|
*/
|
||||||
|
fun getSubs(): List<OptionDataSet>?
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package org.jaaksi.pickerview.dataset
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间:2018年01月31日16:34 <br></br>
|
||||||
|
* 作者:fuchaoyang <br></br>
|
||||||
|
* 描述:数据实现接口,用户显示文案
|
||||||
|
*/
|
||||||
|
interface PickerDataSet {
|
||||||
|
fun getCharSequence(): CharSequence?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return 上传的value,用于匹配初始化选中的下标
|
||||||
|
*/
|
||||||
|
fun getValue(): String?
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
package org.jaaksi.pickerview.dialog
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import org.jaaksi.pickerview.R
|
||||||
|
import org.jaaksi.pickerview.databinding.DialogPickerviewDefaultBinding
|
||||||
|
import org.jaaksi.pickerview.picker.BasePicker
|
||||||
|
|
||||||
|
/**
|
||||||
|
* picker.dialog(IPickerDialog接口)自定义Dialog。提供DefaultPickerDialog,支持全局设定
|
||||||
|
*/
|
||||||
|
class DefaultPickerDialog(context: Context) :
|
||||||
|
BottomSheetDialog(context, R.style.BottomSheetDialog),
|
||||||
|
IPickerDialog {
|
||||||
|
lateinit var picker: BasePicker
|
||||||
|
private set
|
||||||
|
protected var onPickerChooseListener: OnPickerChooseListener? = null
|
||||||
|
|
||||||
|
val binding = DialogPickerviewDefaultBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
init {
|
||||||
|
setContentView(binding.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
behavior.isDraggable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setCanceledOnTouchOutside(sDefaultCanceledOnTouchOutside)
|
||||||
|
binding.btnCancel.setOnClickListener {
|
||||||
|
if (!picker.canSelected()) return@setOnClickListener // 滑动未停止不响应点击事件
|
||||||
|
dismiss()
|
||||||
|
onPickerChooseListener?.onCancel()
|
||||||
|
|
||||||
|
}
|
||||||
|
binding.btnConfirm.setOnClickListener {
|
||||||
|
if (!picker.canSelected()) return@setOnClickListener
|
||||||
|
// 给用户拦截
|
||||||
|
if (onPickerChooseListener == null || onPickerChooseListener!!.onConfirm()) {
|
||||||
|
// 抛给picker去处理
|
||||||
|
dismiss()
|
||||||
|
picker.onConfirm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 先于onCreate(Bundle savedInstanceState)执行
|
||||||
|
*/
|
||||||
|
override fun onCreate(picker: BasePicker) {
|
||||||
|
this.picker = picker
|
||||||
|
binding.root.addView(picker.view())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showDialog() {
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** Canceled dialog OnTouch Outside */
|
||||||
|
var sDefaultCanceledOnTouchOutside = true
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package org.jaaksi.pickerview.dialog
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
interface IGlobalDialogCreator {
|
||||||
|
/**
|
||||||
|
* 创建IPickerDialog
|
||||||
|
*/
|
||||||
|
fun create(context: Context): IPickerDialog
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package org.jaaksi.pickerview.dialog
|
||||||
|
|
||||||
|
import org.jaaksi.pickerview.picker.BasePicker
|
||||||
|
|
||||||
|
interface IPickerDialog {
|
||||||
|
/**
|
||||||
|
* picker create 时回调
|
||||||
|
* @see BasePicker.BasePicker
|
||||||
|
*/
|
||||||
|
fun onCreate(picker: BasePicker)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 其实可以不提供这个方法,为了方便在[BasePicker.show]
|
||||||
|
*/
|
||||||
|
fun showDialog()
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package org.jaaksi.pickerview.dialog
|
||||||
|
|
||||||
|
interface OnPickerChooseListener {
|
||||||
|
/**
|
||||||
|
* @return 是否回调选中关闭dialog
|
||||||
|
*/
|
||||||
|
fun onConfirm(): Boolean
|
||||||
|
fun onCancel()
|
||||||
|
}
|
||||||
@ -0,0 +1,239 @@
|
|||||||
|
package org.jaaksi.pickerview.picker
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.util.SparseArray
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import org.jaaksi.pickerview.dialog.DefaultPickerDialog
|
||||||
|
import org.jaaksi.pickerview.dialog.IGlobalDialogCreator
|
||||||
|
import org.jaaksi.pickerview.dialog.IPickerDialog
|
||||||
|
import org.jaaksi.pickerview.widget.PickerView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间:2018年01月31日18:28 <br></br>
|
||||||
|
* 作者:fuchaoyang <br></br>
|
||||||
|
* BasePicker中并不提供对pickerview的设置方法,而是通过接口PickerHandler转交PickerView处理
|
||||||
|
* 三个picker的的思路有部分是不一样的,如reset调用地方,看看是不是可以优化
|
||||||
|
*/
|
||||||
|
abstract class BasePicker(protected var mContext: Context) {
|
||||||
|
|
||||||
|
/** 是否启用dialog */
|
||||||
|
@JvmField
|
||||||
|
protected var needDialog = true
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
protected var iPickerDialog: IPickerDialog? = null
|
||||||
|
protected lateinit var mPickerContainer: LinearLayout
|
||||||
|
private var mInterceptor: Interceptor? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setTag用法同[View.setTag]
|
||||||
|
*
|
||||||
|
* @param tag tag
|
||||||
|
*/
|
||||||
|
var tag: Any? = null
|
||||||
|
private var mKeyedTags: SparseArray<Any>? = null
|
||||||
|
private val mPickerViews: MutableList<PickerView<*>> = ArrayList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置拦截器,用于用于在pickerview创建时拦截,设置pickerview的属性。Picker内部并不提供对PickerView的设置方法,
|
||||||
|
* 而是通过Interceptor实现,实现Picker和PickerView的属性设置解耦。
|
||||||
|
* 必须在调用 [.createPickerView]之前设置。
|
||||||
|
* 子类应该在Builder中提供该方法。
|
||||||
|
*/
|
||||||
|
protected fun setInterceptor(interceptor: Interceptor?) {
|
||||||
|
mInterceptor = interceptor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置picker背景
|
||||||
|
*
|
||||||
|
* @param color color
|
||||||
|
*/
|
||||||
|
fun setPickerBackgroundColor(@ColorInt color: Int) {
|
||||||
|
mPickerContainer.setBackgroundColor(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置pickerview父容器padding 单位:px
|
||||||
|
*/
|
||||||
|
fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
|
mPickerContainer.setPadding(left, top, right, bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTag(key: Int): Any? {
|
||||||
|
return mKeyedTags?.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun initPickerView() {
|
||||||
|
mPickerContainer = LinearLayout(mContext)
|
||||||
|
mPickerContainer.orientation = LinearLayout.HORIZONTAL
|
||||||
|
mPickerContainer.layoutParams = LinearLayout.LayoutParams(-1, -2)
|
||||||
|
if (sDefaultPaddingRect != null) {
|
||||||
|
setPadding(
|
||||||
|
sDefaultPaddingRect!!.left, sDefaultPaddingRect!!.top, sDefaultPaddingRect!!.right,
|
||||||
|
sDefaultPaddingRect!!.bottom
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (sDefaultPickerBackgroundColor != Color.TRANSPARENT) {
|
||||||
|
setPickerBackgroundColor(sDefaultPickerBackgroundColor)
|
||||||
|
}
|
||||||
|
if (needDialog) { // 是否使用弹窗
|
||||||
|
// 弹窗优先级:自定义的 > 全局的 > 默认的
|
||||||
|
if (iPickerDialog == null) { // 如果没有自定义dialog
|
||||||
|
iPickerDialog = if (sDefaultDialogCreator != null) { // 如果定义了全局的dialog
|
||||||
|
sDefaultDialogCreator!!.create(mContext)
|
||||||
|
} else { // 使用默认的
|
||||||
|
DefaultPickerDialog(mContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iPickerDialog?.onCreate(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [.createPickerView]
|
||||||
|
*
|
||||||
|
* @return Picker中所有的pickerview集合
|
||||||
|
*/
|
||||||
|
val pickerViews: List<PickerView<*>>
|
||||||
|
get() = mPickerViews
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果使用[.createPickerView]创建pickerview,就不需要手动添加
|
||||||
|
*
|
||||||
|
* @param pickerView pickerView
|
||||||
|
*/
|
||||||
|
protected fun addPicker(pickerView: PickerView<*>) {
|
||||||
|
mPickerViews.add(pickerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建pickerview
|
||||||
|
*
|
||||||
|
* @param tag settag
|
||||||
|
* @param weight 权重
|
||||||
|
*/
|
||||||
|
protected fun <T> createPickerView(tag: Any?, weight: Float): PickerView<T> {
|
||||||
|
val pickerView: PickerView<T> = PickerView(mContext)
|
||||||
|
pickerView.tag = tag
|
||||||
|
// 这里是竖直方向的,如果要设置横向的,则自己再设置LayoutParams
|
||||||
|
val params = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT)
|
||||||
|
params.weight = weight
|
||||||
|
// do it
|
||||||
|
if (mInterceptor != null) {
|
||||||
|
mInterceptor!!.intercept(pickerView, params)
|
||||||
|
}
|
||||||
|
pickerView.layoutParams = params
|
||||||
|
mPickerContainer.addView(pickerView)
|
||||||
|
addPicker(pickerView)
|
||||||
|
return pickerView
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过tag找到对应的pickerview
|
||||||
|
*
|
||||||
|
* @param tag tag
|
||||||
|
* @return 对应tag的pickerview,找不到返回null
|
||||||
|
*/
|
||||||
|
fun findPickerViewByTag(tag: Any): PickerView<*>? {
|
||||||
|
for (pickerView in mPickerViews) {
|
||||||
|
if (checkIsSamePickerView(tag, pickerView.tag)) return pickerView
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过两个tag判断是否是同一个pickerview
|
||||||
|
*/
|
||||||
|
protected fun checkIsSamePickerView(tag: Any, pickerViewTag: Any): Boolean {
|
||||||
|
return tag == pickerViewTag
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否滚动未停止
|
||||||
|
*/
|
||||||
|
fun canSelected(): Boolean {
|
||||||
|
for (i in mPickerViews.indices.reversed()) {
|
||||||
|
val pickerView = mPickerViews[i]
|
||||||
|
if (!pickerView.canSelected()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setTag 用法同[View.setTag]
|
||||||
|
*
|
||||||
|
* @param key key R.id.xxx
|
||||||
|
* @param tag tag
|
||||||
|
*/
|
||||||
|
fun setTag(key: Int, tag: Any) {
|
||||||
|
// If the package id is 0x00 or 0x01, it's either an undefined package
|
||||||
|
// or a framework id
|
||||||
|
require(key ushr 24 >= 2) { "The key must be an application-specific " + "resource id." }
|
||||||
|
setKeyedTag(key, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setKeyedTag(key: Int, tag: Any) {
|
||||||
|
if (mKeyedTags == null) {
|
||||||
|
mKeyedTags = SparseArray(2)
|
||||||
|
}
|
||||||
|
mKeyedTags!!.put(key, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return 获取IPickerDialog
|
||||||
|
*/
|
||||||
|
fun dialog(): IPickerDialog? {
|
||||||
|
return iPickerDialog
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return 获取picker的view,用于非弹窗情况
|
||||||
|
*/
|
||||||
|
fun view(): LinearLayout {
|
||||||
|
return mPickerContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示picker弹窗
|
||||||
|
*/
|
||||||
|
fun show() {
|
||||||
|
iPickerDialog?.showDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击确定按钮的回调
|
||||||
|
*/
|
||||||
|
abstract fun onConfirm()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于子类修改设置PickerView属性
|
||||||
|
*/
|
||||||
|
fun interface Interceptor {
|
||||||
|
/**
|
||||||
|
* 拦截pickerview的创建,我们可以自定义
|
||||||
|
*
|
||||||
|
* @param pickerView 增加layoutparams参数,方便设置weight
|
||||||
|
*/
|
||||||
|
fun intercept(pickerView: PickerView<*>, params: LinearLayout.LayoutParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** pickerView父容器的 default padding */
|
||||||
|
var sDefaultPaddingRect: Rect? = null
|
||||||
|
|
||||||
|
/** default picker background color */
|
||||||
|
var sDefaultPickerBackgroundColor = Color.WHITE
|
||||||
|
|
||||||
|
/** Canceled dialog OnTouch Outside */
|
||||||
|
var sDefaultCanceledOnTouchOutside = true
|
||||||
|
|
||||||
|
/** 用于构建全局的DefaultDialog的接口 */
|
||||||
|
var sDefaultDialogCreator: IGlobalDialogCreator? = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,238 @@
|
|||||||
|
package org.jaaksi.pickerview.picker
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.jaaksi.pickerview.dataset.OptionDataSet
|
||||||
|
import org.jaaksi.pickerview.dialog.IPickerDialog
|
||||||
|
import org.jaaksi.pickerview.picker.option.ForeignOptionDelegate
|
||||||
|
import org.jaaksi.pickerview.picker.option.IOptionDelegate
|
||||||
|
import org.jaaksi.pickerview.picker.option.OptionDelegate
|
||||||
|
import org.jaaksi.pickerview.widget.BasePickerView
|
||||||
|
import org.jaaksi.pickerview.widget.BasePickerView.OnSelectedListener
|
||||||
|
import org.jaaksi.pickerview.widget.PickerView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by fuchaoyang on 2018/2/11.<br></br>
|
||||||
|
* description:多级别的的选项picker,支持联动,与非联动
|
||||||
|
* 强大点:
|
||||||
|
* 与https://github.com/Bigkoo/Android-PickerView对比
|
||||||
|
* 1.支持设置层级
|
||||||
|
* 2.构造数据源简单,只需要实现OptionDataSet接口
|
||||||
|
* 3.支持联动及不联动
|
||||||
|
* 3.支持通过选中的value设置选中项,内部处理选中项逻辑,避免用户麻烦的遍历处理
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class OptionPicker private constructor(
|
||||||
|
context: Context,
|
||||||
|
private val hierarchy: Int,
|
||||||
|
private val onOptionSelectListener: OnOptionSelectListener
|
||||||
|
) : BasePicker(context), OnSelectedListener, BasePickerView.Formatter {
|
||||||
|
// 层级,有几层add几个pickerview
|
||||||
|
|
||||||
|
// 选中的下标。如果为-1,表示当前选中的index列没有数据
|
||||||
|
/**
|
||||||
|
* 获取选中的下标
|
||||||
|
*
|
||||||
|
* @return 选中的下标,数组size=mHierarchy,如果为-1表示该列没有数据
|
||||||
|
*/
|
||||||
|
val selectedPosition: IntArray = IntArray(this.hierarchy)
|
||||||
|
|
||||||
|
/** 是否无关连 */
|
||||||
|
private var mIsForeign = false
|
||||||
|
private var mFormatter: Formatter? = null
|
||||||
|
private var mDelegate: IOptionDelegate? = null
|
||||||
|
|
||||||
|
private fun initPicker() {
|
||||||
|
for (i in 0 until this.hierarchy) {
|
||||||
|
val pickerView: PickerView<*> = createPickerView<Any>(i, 1f)
|
||||||
|
pickerView.setOnSelectedListener(this)
|
||||||
|
pickerView.formatter = this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFormatter(formatter: Formatter?) {
|
||||||
|
mFormatter = formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initForeign(foreign: Boolean) {
|
||||||
|
mIsForeign = foreign
|
||||||
|
mDelegate = if (mIsForeign) { // 不关联的
|
||||||
|
ForeignOptionDelegate()
|
||||||
|
} else {
|
||||||
|
OptionDelegate()
|
||||||
|
}
|
||||||
|
mDelegate!!.init(object : Delegate {
|
||||||
|
override val hierarchy: Int
|
||||||
|
get() = this@OptionPicker.hierarchy
|
||||||
|
override val selectedPosition: IntArray
|
||||||
|
get() = this@OptionPicker.selectedPosition
|
||||||
|
|
||||||
|
override fun getPickerViews(): List<PickerView<OptionDataSet>> {
|
||||||
|
return pickerViews as List<PickerView<OptionDataSet>>
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据选中的values初始化选中的position并初始化pickerview数据
|
||||||
|
*
|
||||||
|
* @param options data
|
||||||
|
*/
|
||||||
|
fun setData(vararg options: List<OptionDataSet>) {
|
||||||
|
// 初始化是否关联
|
||||||
|
initForeign(options.size > 1)
|
||||||
|
mDelegate!!.setData(*options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据选中的values初始化选中的position
|
||||||
|
*
|
||||||
|
* @param values 选中数据的value[OptionDataSet.getValue],如果values[0]==null,则进行默认选中,其他为null认为没有该列
|
||||||
|
*/
|
||||||
|
fun setSelectedWithValues(vararg values: String?) {
|
||||||
|
mDelegate!!.setSelectedWithValues(*values)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun reset() {
|
||||||
|
mDelegate!!.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectedOptions: Array<OptionDataSet?>
|
||||||
|
/**
|
||||||
|
* 获取选中的选项
|
||||||
|
*
|
||||||
|
* @return 选中的选项,如果指定index为null则表示该列没有数据
|
||||||
|
*/
|
||||||
|
get() = mDelegate!!.selectedOptions
|
||||||
|
|
||||||
|
override fun onConfirm() {
|
||||||
|
onOptionSelectListener.onOptionSelect(this, this.selectedPosition, selectedOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置选中的position
|
||||||
|
private fun resetPosition(index: Int, position: Int) {
|
||||||
|
for (i in index until selectedPosition.size) {
|
||||||
|
if (i == index) {
|
||||||
|
selectedPosition[i] = position
|
||||||
|
} else {
|
||||||
|
if (!mIsForeign) {
|
||||||
|
// 如果是无关的则不需要处理后面的index,关联的则直接重置为0
|
||||||
|
selectedPosition[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSelected(pickerView: BasePickerView<*>, position: Int) {
|
||||||
|
// 1联动2联动3...当前选中position,后面的都重置为0,更改mSelectedPosition,然后直接reset
|
||||||
|
val index = pickerView.tag as Int
|
||||||
|
resetPosition(index, position)
|
||||||
|
reset()
|
||||||
|
if (!needDialog){
|
||||||
|
onConfirm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun format(
|
||||||
|
pickerView: BasePickerView<*>,
|
||||||
|
position: Int,
|
||||||
|
charSequence: CharSequence?
|
||||||
|
): CharSequence? {
|
||||||
|
return if (mFormatter == null) charSequence else mFormatter!!.format(
|
||||||
|
this,
|
||||||
|
pickerView.tag as Int,
|
||||||
|
position,
|
||||||
|
charSequence
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制设置的属性直接在构造方法中设置
|
||||||
|
*
|
||||||
|
* @param hierarchy 层级,有几层add几个pickerview
|
||||||
|
* @param listener listener
|
||||||
|
*/
|
||||||
|
class Builder(
|
||||||
|
private val context: Context,
|
||||||
|
private val hierarchy: Int,
|
||||||
|
private val onOptionSelectListener: OnOptionSelectListener
|
||||||
|
) {
|
||||||
|
private var mInterceptor: Interceptor? = null
|
||||||
|
private var mFormatter: Formatter? = null
|
||||||
|
private var needDialog = true
|
||||||
|
private var iPickerDialog: IPickerDialog? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置内容 Formatter
|
||||||
|
*
|
||||||
|
* @param formatter formatter
|
||||||
|
*/
|
||||||
|
fun setFormatter(formatter: Formatter?): Builder {
|
||||||
|
mFormatter = formatter
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置拦截器
|
||||||
|
*
|
||||||
|
* @param interceptor 拦截器
|
||||||
|
*/
|
||||||
|
fun setInterceptor(interceptor: Interceptor?): Builder {
|
||||||
|
mInterceptor = interceptor
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义弹窗,如果为null表示不需要弹窗
|
||||||
|
* @param iPickerDialog
|
||||||
|
*/
|
||||||
|
fun dialog(iPickerDialog: IPickerDialog?): Builder {
|
||||||
|
needDialog = iPickerDialog != null
|
||||||
|
this.iPickerDialog = iPickerDialog
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(): OptionPicker {
|
||||||
|
val picker = OptionPicker(context, hierarchy, onOptionSelectListener)
|
||||||
|
picker.needDialog = needDialog
|
||||||
|
picker.iPickerDialog = iPickerDialog
|
||||||
|
picker.initPickerView()
|
||||||
|
picker.setFormatter(mFormatter)
|
||||||
|
picker.setInterceptor(mInterceptor)
|
||||||
|
picker.initPicker()
|
||||||
|
return picker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Formatter {
|
||||||
|
/**
|
||||||
|
* @param level 级别 0 ~ mHierarchy - 1
|
||||||
|
* @param charSequence charSequence
|
||||||
|
*/
|
||||||
|
fun format(
|
||||||
|
picker: OptionPicker,
|
||||||
|
level: Int,
|
||||||
|
position: Int,
|
||||||
|
charSequence: CharSequence?
|
||||||
|
): CharSequence?
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnOptionSelectListener {
|
||||||
|
/**
|
||||||
|
* @param selectedPosition length = mHierarchy。选中的下标:如果指定index为-1,表示当前选中的index列没有数据
|
||||||
|
* @param selectedOptions length = mHierarchy。选中的选项,如果指定index为null则表示该列没有数据
|
||||||
|
*/
|
||||||
|
fun onOptionSelect(
|
||||||
|
picker: OptionPicker, selectedPosition: IntArray,
|
||||||
|
selectedOptions: Array<OptionDataSet?>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Delegate {
|
||||||
|
val hierarchy: Int
|
||||||
|
val selectedPosition: IntArray
|
||||||
|
fun getPickerViews(): List<PickerView<OptionDataSet>>
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,934 @@
|
|||||||
|
package org.jaaksi.pickerview.picker
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.jaaksi.pickerview.adapter.ArrayWheelAdapter
|
||||||
|
import org.jaaksi.pickerview.adapter.NumericWheelAdapter
|
||||||
|
import org.jaaksi.pickerview.dialog.IPickerDialog
|
||||||
|
import org.jaaksi.pickerview.util.DateUtil
|
||||||
|
import org.jaaksi.pickerview.util.DateUtil.createDateFormat
|
||||||
|
import org.jaaksi.pickerview.util.DateUtil.getDayOfMonth
|
||||||
|
import org.jaaksi.pickerview.widget.BasePickerView
|
||||||
|
import org.jaaksi.pickerview.widget.BasePickerView.OnSelectedListener
|
||||||
|
import org.jaaksi.pickerview.widget.PickerView
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间:2018年08月02日15:42 <br></br>
|
||||||
|
* 作者:fuchaoyang <br></br>
|
||||||
|
* 描述:时间选择器
|
||||||
|
* 强大点:
|
||||||
|
* 1.type的设计,自由组合
|
||||||
|
* 2.支持时间区间设置以及选中联动
|
||||||
|
* 3.支持混合模式,支持日期,时间混合
|
||||||
|
* 4.支持自定义日期、时间格式
|
||||||
|
* 5.time支持设置时间间隔,如30分钟。也就是00:00 00:30 01:00 01:30
|
||||||
|
* ,无法被60整除的,如设置13分钟,认为是无效设置,会被忽略。如13 26 39 52,只能选到52
|
||||||
|
*/
|
||||||
|
class TimePicker private constructor(
|
||||||
|
context: Context,
|
||||||
|
private val type: Int,
|
||||||
|
private val onTimeSelectListener: OnTimeSelectListener
|
||||||
|
) : BasePicker(context), OnSelectedListener, BasePickerView.Formatter {
|
||||||
|
private var mDatePicker: PickerView<Int>? = null
|
||||||
|
private var mYearPicker: PickerView<Int>? = null
|
||||||
|
private var mMonthPicker: PickerView<Int>? = null
|
||||||
|
private var mDayPicker: PickerView<Int>? = null
|
||||||
|
private var mTimePicker: PickerView<Int>? = null
|
||||||
|
private var mHourPicker: PickerView<Int>? = null
|
||||||
|
private var mNoonPicker: PickerView<Int>? = null
|
||||||
|
private var mMinutePicker: PickerView<Int>? = null
|
||||||
|
|
||||||
|
// 初始设置选中的时间,如果不设置为startDate
|
||||||
|
private var mSelectedDate: Calendar? = null
|
||||||
|
private lateinit var mStartDate: Calendar //开始时间
|
||||||
|
private lateinit var mEndDate: Calendar //终止时间
|
||||||
|
|
||||||
|
// 聚合的日期模式
|
||||||
|
private var mDayOffset = -1
|
||||||
|
private var mStartYear = 0
|
||||||
|
private var mEndYear = 0
|
||||||
|
private var mStartMonth = 0
|
||||||
|
private var mEndMonth = 0
|
||||||
|
private var mStartDay = 0
|
||||||
|
private var mEndDay = 0
|
||||||
|
private var mStartHour = 0
|
||||||
|
private var mEndHour = 0
|
||||||
|
private var mStartMinute = 0
|
||||||
|
private var mEndMinute = 0
|
||||||
|
|
||||||
|
// 时间分钟间隔
|
||||||
|
private var mTimeMinuteOffset = 0
|
||||||
|
|
||||||
|
// 设置offset时,是否包含起止时间
|
||||||
|
private var mContainsStarDate = false
|
||||||
|
private var mContainsEndDate = false
|
||||||
|
var formatter: Formatter? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar 指定时间
|
||||||
|
* @param isStart 是否是起始时间
|
||||||
|
* @return 指定时间的有效分钟偏移量
|
||||||
|
*/
|
||||||
|
private fun getValidTimeOffset(
|
||||||
|
calendar: Calendar, isStart: Boolean
|
||||||
|
): Int {
|
||||||
|
val timeMinutes = calendar[Calendar.MINUTE]
|
||||||
|
var validOffset: Int
|
||||||
|
val offset = timeMinutes % mTimeMinuteOffset
|
||||||
|
if (offset == 0) {
|
||||||
|
validOffset = 0
|
||||||
|
} else {
|
||||||
|
validOffset = -offset
|
||||||
|
if (isStart) {
|
||||||
|
if (!mContainsStarDate) {
|
||||||
|
validOffset += mTimeMinuteOffset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mContainsEndDate) {
|
||||||
|
validOffset += mTimeMinuteOffset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ignoreSecond(calendar: Calendar) {
|
||||||
|
calendar[Calendar.SECOND] = 0
|
||||||
|
calendar[Calendar.MILLISECOND] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setRangDate(startDate: Long, endDate: Long) {
|
||||||
|
// 重新计算时间区间,bugfix:由于由于起始时间没有考虑时间间隔而导致可能会引起bug
|
||||||
|
var calendar = Calendar.getInstance()
|
||||||
|
calendar.timeInMillis = startDate
|
||||||
|
ignoreSecond(calendar)
|
||||||
|
calendar.add(Calendar.MINUTE, getValidTimeOffset(calendar, true))
|
||||||
|
mStartDate = calendar
|
||||||
|
calendar = Calendar.getInstance()
|
||||||
|
calendar.timeInMillis = endDate
|
||||||
|
ignoreSecond(calendar)
|
||||||
|
calendar.add(Calendar.MINUTE, getValidTimeOffset(calendar, false))
|
||||||
|
mEndDate = calendar
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置选中的时间,如果不设置默认为起始时间,应该伴随着show方法使用
|
||||||
|
*
|
||||||
|
* @param millis 选中的时间戳 单位ms
|
||||||
|
*/
|
||||||
|
fun setSelectedDate(millis: Long) {
|
||||||
|
updateSelectedDate(millis)
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSelectedDate(millis: Long) {
|
||||||
|
if (mSelectedDate == null) {
|
||||||
|
mSelectedDate = Calendar.getInstance()
|
||||||
|
}
|
||||||
|
mSelectedDate!!.timeInMillis = millis
|
||||||
|
ignoreSecond(mSelectedDate!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return 是否有上下午,并且是下午
|
||||||
|
*/
|
||||||
|
private val isAfterNoon: Boolean
|
||||||
|
private get() = hasType(TYPE_12_HOUR) && mNoonPicker!!.selectedItem == 1
|
||||||
|
|
||||||
|
val selectedDates: Date
|
||||||
|
get() {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
if (hasType(TYPE_MIXED_DATE)) {
|
||||||
|
calendar.timeInMillis = mStartDate.timeInMillis
|
||||||
|
calendar.add(Calendar.DAY_OF_YEAR, mDatePicker!!.selectedPosition)
|
||||||
|
} else {
|
||||||
|
calendar.time = mSelectedDate!!.time
|
||||||
|
if (hasType(TYPE_YEAR)) {
|
||||||
|
calendar[Calendar.YEAR] = mYearPicker!!.selectedItem!!
|
||||||
|
}
|
||||||
|
if (hasType(TYPE_MONTH)) {
|
||||||
|
calendar[Calendar.MONTH] = mMonthPicker!!.selectedItem!! - 1
|
||||||
|
}
|
||||||
|
if (hasType(TYPE_DAY)) {
|
||||||
|
calendar[Calendar.DAY_OF_MONTH] = mDayPicker!!.selectedItem!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasType(TYPE_MIXED_TIME)) {
|
||||||
|
var hour = mTimePicker!!.selectedItem!! * mTimeMinuteOffset / 60
|
||||||
|
if (isAfterNoon) { // 下午
|
||||||
|
hour += 12
|
||||||
|
}
|
||||||
|
calendar[Calendar.HOUR_OF_DAY] = hour
|
||||||
|
val minute = mTimePicker!!.selectedItem!! * mTimeMinuteOffset % 60
|
||||||
|
calendar[Calendar.MINUTE] = minute
|
||||||
|
} else {
|
||||||
|
if (hasType(TYPE_HOUR)) {
|
||||||
|
val hour =
|
||||||
|
if (isAfterNoon) mHourPicker!!.selectedItem!! + 12 else mHourPicker!!.selectedItem!!
|
||||||
|
calendar[Calendar.HOUR_OF_DAY] = hour
|
||||||
|
}
|
||||||
|
if (hasType(TYPE_MINUTE)) {
|
||||||
|
calendar[Calendar.MINUTE] = getRealMinute(mMinutePicker!!.selectedPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return calendar.time
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param type type
|
||||||
|
* @return 是否包含类型 type
|
||||||
|
*/
|
||||||
|
fun hasType(type: Int): Boolean {
|
||||||
|
return this.type and type == type
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* createPickerView在init中执行,那么[.setInterceptor]就必须在构造该方法之前执行才有效。采用Builder
|
||||||
|
*/
|
||||||
|
private fun initPicker() {
|
||||||
|
if (hasType(TYPE_MIXED_DATE)) {
|
||||||
|
mDatePicker = createPickerView(TYPE_MIXED_DATE, 2.5f)
|
||||||
|
mDatePicker!!.setOnSelectedListener(this)
|
||||||
|
mDatePicker!!.formatter = this
|
||||||
|
} else {
|
||||||
|
if (hasType(TYPE_YEAR)) {
|
||||||
|
mYearPicker = createPickerView(TYPE_YEAR, 1.2f)
|
||||||
|
mYearPicker!!.setOnSelectedListener(this)
|
||||||
|
mYearPicker!!.formatter = this
|
||||||
|
}
|
||||||
|
if (hasType(TYPE_MONTH)) {
|
||||||
|
mMonthPicker = createPickerView(TYPE_MONTH, 1f)
|
||||||
|
mMonthPicker!!.setOnSelectedListener(this)
|
||||||
|
mMonthPicker!!.formatter = this
|
||||||
|
}
|
||||||
|
if (hasType(TYPE_DAY)) {
|
||||||
|
mDayPicker = createPickerView(TYPE_DAY, 1f)
|
||||||
|
mDayPicker!!.setOnSelectedListener(this)
|
||||||
|
mDayPicker!!.formatter = this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasType(TYPE_12_HOUR)) { // 上下午
|
||||||
|
mNoonPicker = createPickerView(TYPE_12_HOUR, 1f)
|
||||||
|
mNoonPicker!!.setOnSelectedListener(this)
|
||||||
|
mNoonPicker!!.formatter = this
|
||||||
|
}
|
||||||
|
if (hasType(TYPE_MIXED_TIME)) { // 包含Time
|
||||||
|
mTimePicker = createPickerView(TYPE_MIXED_TIME, 2f)
|
||||||
|
mTimePicker!!.formatter = this
|
||||||
|
mTimePicker!!.setOnSelectedListener(this)
|
||||||
|
} else {
|
||||||
|
if (hasType(TYPE_HOUR)) {
|
||||||
|
mHourPicker = createPickerView(TYPE_HOUR, 1f)
|
||||||
|
mHourPicker!!.setOnSelectedListener(this)
|
||||||
|
mHourPicker!!.formatter = this
|
||||||
|
if (hasType(TYPE_12_HOUR)) { // 如果是12小时制,将小时设置为循环的
|
||||||
|
mHourPicker!!.setIsCirculation(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasType(TYPE_MINUTE)) {
|
||||||
|
mMinutePicker = createPickerView(TYPE_MINUTE, 1f)
|
||||||
|
mMinutePicker!!.formatter = this
|
||||||
|
mMinutePicker!!.setOnSelectedListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleData() {
|
||||||
|
if (mSelectedDate == null || mSelectedDate!!.timeInMillis < mStartDate.timeInMillis) {
|
||||||
|
updateSelectedDate(mStartDate.timeInMillis)
|
||||||
|
} else if (mSelectedDate!!.timeInMillis > mEndDate.timeInMillis) {
|
||||||
|
updateSelectedDate(mEndDate.timeInMillis)
|
||||||
|
}
|
||||||
|
if (mTimeMinuteOffset < 1) {
|
||||||
|
mTimeMinuteOffset = 1
|
||||||
|
}
|
||||||
|
// 因为区间不能改变,所以这里只进行一次初始化操作
|
||||||
|
if (mDayOffset == -1 || mStartYear == 0) {
|
||||||
|
if (hasType(TYPE_MIXED_DATE)) {
|
||||||
|
mDayOffset = offsetStart(mEndDate)
|
||||||
|
} else {
|
||||||
|
mStartYear = mStartDate[Calendar.YEAR]
|
||||||
|
mEndYear = mEndDate[Calendar.YEAR]
|
||||||
|
mStartMonth = mStartDate[Calendar.MONTH] + 1
|
||||||
|
mEndMonth = mEndDate[Calendar.MONTH] + 1
|
||||||
|
mStartDay = mStartDate[Calendar.DAY_OF_MONTH]
|
||||||
|
mEndDay = mEndDate[Calendar.DAY_OF_MONTH]
|
||||||
|
}
|
||||||
|
mStartHour = mStartDate[Calendar.HOUR_OF_DAY]
|
||||||
|
mEndHour = mEndDate[Calendar.HOUR_OF_DAY]
|
||||||
|
mStartMinute = mStartDate[Calendar.MINUTE]
|
||||||
|
mEndMinute = mEndDate[Calendar.MINUTE]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reset() {
|
||||||
|
handleData()
|
||||||
|
// 处理数据,根据当前选中的时间及设置的日期范围处理数据
|
||||||
|
if (hasType(TYPE_MIXED_DATE)) {
|
||||||
|
if (mDatePicker!!.adapter == null) {
|
||||||
|
mDatePicker!!.adapter = NumericWheelAdapter(0, mDayOffset)
|
||||||
|
}
|
||||||
|
mDatePicker!!.setSelectedPosition(offsetStart(mSelectedDate!!), false)
|
||||||
|
if (hasType(TYPE_12_HOUR)) {
|
||||||
|
resetNoonAdapter(true)
|
||||||
|
}
|
||||||
|
if (hasType(TYPE_MIXED_TIME)) {
|
||||||
|
// 时间需要考虑起始日期对应的起始时间
|
||||||
|
resetTimeAdapter(true)
|
||||||
|
} else {
|
||||||
|
resetHourAdapter(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hasType(TYPE_YEAR)) {
|
||||||
|
if (mYearPicker!!.adapter == null) { // 年不会发生变化,不需要重复设置
|
||||||
|
mYearPicker!!.adapter =
|
||||||
|
NumericWheelAdapter(mStartDate[Calendar.YEAR], mEndDate[Calendar.YEAR])
|
||||||
|
}
|
||||||
|
mYearPicker!!.setSelectedPosition(
|
||||||
|
mSelectedDate!![Calendar.YEAR] - mYearPicker!!.adapter!!.getItem(0)!!, false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
resetMonthAdapter(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetMonthAdapter(isInit: Boolean) {
|
||||||
|
// 1.根据当前选中的年份,以及起止时间,设置对应的月份。然后再设置对应的日
|
||||||
|
if (hasType(TYPE_MONTH)) {
|
||||||
|
val year =
|
||||||
|
if (hasType(TYPE_YEAR)) mYearPicker!!.selectedItem!! else mSelectedDate!![Calendar.YEAR]
|
||||||
|
val start: Int
|
||||||
|
val end: Int
|
||||||
|
// 这里要计算 selectedItem 而不是selectedPosition
|
||||||
|
val last =
|
||||||
|
if (isInit) mSelectedDate!![Calendar.MONTH] + 1 else mMonthPicker!!.selectedItem!!
|
||||||
|
start = if (year == mStartYear) mStartMonth else 1
|
||||||
|
end = if (year == mEndYear) mEndMonth else 12
|
||||||
|
mMonthPicker!!.adapter = NumericWheelAdapter(start, end)
|
||||||
|
// 2.设置选中的月份
|
||||||
|
mMonthPicker!!.setSelectedPosition(last - mMonthPicker!!.adapter!!.getItem(0)!!, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.月份要联动日
|
||||||
|
resetDayAdapter(isInit)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetDayAdapter(isInit: Boolean) {
|
||||||
|
if (hasType(TYPE_DAY)) {
|
||||||
|
val year =
|
||||||
|
if (hasType(TYPE_YEAR)) mYearPicker!!.selectedItem!! else mSelectedDate!![Calendar.YEAR]
|
||||||
|
// 3.根据当前选中的年月设置日期。联动同月份。如果和起始或截止时同一年月,则比较对应日期
|
||||||
|
// 有年,有日,则强制认为有月。
|
||||||
|
val month =
|
||||||
|
if (hasType(TYPE_MONTH)) mMonthPicker!!.selectedItem!! else mSelectedDate!![Calendar.MONTH] + 1
|
||||||
|
val last =
|
||||||
|
if (isInit) mSelectedDate!![Calendar.DAY_OF_MONTH] else mDayPicker!!.selectedItem!!
|
||||||
|
val start = if (year == mStartYear && month == mStartMonth) mStartDay else 1
|
||||||
|
val end =
|
||||||
|
if (year == mEndYear && month == mEndMonth) mEndDay else getDayOfMonth(year, month)
|
||||||
|
mDayPicker!!.adapter = NumericWheelAdapter(start, end)
|
||||||
|
mDayPicker!!.setSelectedPosition(last - mDayPicker!!.adapter!!.getItem(0)!!, false)
|
||||||
|
}
|
||||||
|
resetNoonAdapter(isInit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择上下午的时候,如果选中的是起止时间,要重置下一级(hour or timeadapter)
|
||||||
|
* 比如起始时间是 9:30,如果是上午,则hour是9-11,如果是下午则是0-11,非起止时间都是0-11
|
||||||
|
* 如果结束时间
|
||||||
|
*/
|
||||||
|
private fun resetNoonAdapter(isInit: Boolean) {
|
||||||
|
if (hasType(TYPE_12_HOUR)) {
|
||||||
|
val isSameStartDay = isSameDay(true)
|
||||||
|
val isSameEndDay = isSameDay(false)
|
||||||
|
val noons: MutableList<Int> = ArrayList()
|
||||||
|
if (!isSameStartDay || mStartHour < 12) { // 如果是起始的那天,且时间>11点,则不包含上午
|
||||||
|
noons.add(0)
|
||||||
|
}
|
||||||
|
if (!isSameEndDay || mEndHour >= 12) { // 如果是结束的那天,且时间<12点,则不包含下午
|
||||||
|
noons.add(1)
|
||||||
|
}
|
||||||
|
val last: Int = if (isInit) {
|
||||||
|
if (mSelectedDate!![Calendar.HOUR_OF_DAY] < 12) 0 else 1
|
||||||
|
} else {
|
||||||
|
mNoonPicker!!.selectedItem!!
|
||||||
|
}
|
||||||
|
mNoonPicker!!.adapter = ArrayWheelAdapter(noons)
|
||||||
|
mNoonPicker!!.setSelectedPosition(last, false)
|
||||||
|
}
|
||||||
|
if (hasType(TYPE_MIXED_TIME)) {
|
||||||
|
resetTimeAdapter(isInit)
|
||||||
|
} else {
|
||||||
|
// 日联动小时
|
||||||
|
resetHourAdapter(isInit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetTimeAdapter(isInit: Boolean) { // 如果是聚合日期+12小时制+聚合时间就crash了。因为没有初始化上下午adapter
|
||||||
|
val isSameStartDay = isSameDay(true)
|
||||||
|
val isSameEndDay = isSameDay(false)
|
||||||
|
val start: Int
|
||||||
|
val end: Int
|
||||||
|
if (!hasType(TYPE_12_HOUR)) {
|
||||||
|
start = if (isSameStartDay) getValidTimeMinutes(mStartDate, true) else 0
|
||||||
|
end = if (isSameEndDay) getValidTimeMinutes(
|
||||||
|
mEndDate, false
|
||||||
|
) else getValidTimeMinutes(24 * 60 - mTimeMinuteOffset, false)
|
||||||
|
} else {
|
||||||
|
if (isSameStartDay) {
|
||||||
|
// 如果起始时间是上午,并且选择的是下午,start=0,否则start=get12Hour(start)
|
||||||
|
start = if (mStartHour < 12 && mNoonPicker!!.selectedItem == 1) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
if (mStartHour >= 12) getValidTimeMinutes(
|
||||||
|
mStartDate, true
|
||||||
|
) - 12 * 60 else getValidTimeMinutes(mStartDate, true)
|
||||||
|
}
|
||||||
|
end = if (isSameEndDay && mEndHour >= 12 && mNoonPicker!!.selectedItem == 1) {
|
||||||
|
// 如果 > 12 需要减去12小时
|
||||||
|
if (mEndHour >= 12) getValidTimeMinutes(
|
||||||
|
mEndDate, false
|
||||||
|
) - 12 * 60 else getValidTimeMinutes(
|
||||||
|
mEndDate, false
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
getValidTimeMinutes(12 * 60 - mTimeMinuteOffset, false)
|
||||||
|
}
|
||||||
|
} else if (isSameEndDay) {
|
||||||
|
start = 0
|
||||||
|
end = if (mEndHour >= 12 && mNoonPicker!!.selectedItem == 1) {
|
||||||
|
// 如果 > 12 需要减去12小时
|
||||||
|
if (mEndHour >= 12) getValidTimeMinutes(
|
||||||
|
mEndDate, false
|
||||||
|
) - 12 * 60 else getValidTimeMinutes(
|
||||||
|
mEndDate, false
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
getValidTimeMinutes(12 * 60 - mTimeMinuteOffset, false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
start = 0
|
||||||
|
end = getValidTimeMinutes(12 * 60 - mTimeMinuteOffset, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val last: Int = if (isInit) {
|
||||||
|
if (hasType(TYPE_12_HOUR)) {
|
||||||
|
val timeMinutes = getValidTimeMinutes(mSelectedDate, true)
|
||||||
|
if (timeMinutes >= 12 * 60) getValidTimeMinutes(
|
||||||
|
mSelectedDate, true
|
||||||
|
) - 12 * 60 else getValidTimeMinutes(mSelectedDate, true)
|
||||||
|
} else {
|
||||||
|
getValidTimeMinutes(mSelectedDate, true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mTimePicker!!.selectedItem!! * mTimeMinuteOffset
|
||||||
|
}
|
||||||
|
// adapter 的item设置的是 有效分钟数/mTimeMinuteOffset
|
||||||
|
mTimePicker!!.adapter =
|
||||||
|
NumericWheelAdapter(getValidTimesValue(start), getValidTimesValue(end))
|
||||||
|
mTimePicker!!.setSelectedPosition(findPositionByValidTimes(last), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetHourAdapter(isInit: Boolean) {
|
||||||
|
if (hasType(TYPE_HOUR)) {
|
||||||
|
val isSameStartDay = isSameDay(true)
|
||||||
|
val isSameEndDay = isSameDay(false)
|
||||||
|
val start: Int
|
||||||
|
val end: Int
|
||||||
|
if (!hasType(TYPE_12_HOUR)) {
|
||||||
|
start = if (isSameStartDay) mStartHour else 0
|
||||||
|
end = if (isSameEndDay) mEndHour else 23
|
||||||
|
} else {
|
||||||
|
if (isSameStartDay) {
|
||||||
|
// 如果起始时间是上午,并且选择的是下午,start=0,否则start=get12Hour(start)
|
||||||
|
start = if (mStartHour < 12 && mNoonPicker!!.selectedItem == 1) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
get12Hour(mStartHour)
|
||||||
|
}
|
||||||
|
end =
|
||||||
|
if (isSameEndDay && mEndHour >= 12 && mNoonPicker!!.selectedItem == 1) { // 如果开始和结束时间是同一天
|
||||||
|
get12Hour(mEndHour)
|
||||||
|
} else {
|
||||||
|
11
|
||||||
|
}
|
||||||
|
} else if (isSameEndDay) {
|
||||||
|
start = 0
|
||||||
|
// 如果截止时间是下午,如果选择的是上午,end=11,如果选择的下午,end=get12Hour(mEndHour)
|
||||||
|
// 如果截止时间是上午,选择的是上午,end=get12Hour
|
||||||
|
end = if (mEndHour >= 12 && mNoonPicker!!.selectedItem == 1) {
|
||||||
|
get12Hour(mEndHour)
|
||||||
|
} else {
|
||||||
|
11
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
start = 0
|
||||||
|
end = 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val last: Int = if (isInit) {
|
||||||
|
if (hasType(TYPE_12_HOUR)) {
|
||||||
|
get12Hour(mSelectedDate!![Calendar.HOUR_OF_DAY])
|
||||||
|
} else {
|
||||||
|
mSelectedDate!![Calendar.HOUR_OF_DAY]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mHourPicker!!.selectedItem!!
|
||||||
|
}
|
||||||
|
mHourPicker!!.adapter = NumericWheelAdapter(start, end)
|
||||||
|
mHourPicker!!.setSelectedPosition(last - mHourPicker!!.adapter!!.getItem(0)!!, false)
|
||||||
|
}
|
||||||
|
resetMinuteAdapter(isInit)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun get12Hour(hour: Int): Int {
|
||||||
|
return if (hour >= 12) {
|
||||||
|
hour - 12
|
||||||
|
} else hour
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isSameDay(isStart: Boolean): Boolean {
|
||||||
|
return if (hasType(TYPE_MIXED_DATE)) {
|
||||||
|
if (isStart) {
|
||||||
|
DateUtil.isSameDay(mStartDate.timeInMillis,selectedDate.time)
|
||||||
|
} else {
|
||||||
|
DateUtil.isSameDay(mEndDate.timeInMillis, selectedDate.time)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val year =
|
||||||
|
if (hasType(TYPE_YEAR)) mYearPicker!!.selectedItem!! else mSelectedDate!![Calendar.YEAR]
|
||||||
|
val month =
|
||||||
|
if (hasType(TYPE_MONTH)) mMonthPicker!!.selectedItem!! else mSelectedDate!![Calendar.MONTH] + 1
|
||||||
|
val day =
|
||||||
|
if (hasType(TYPE_DAY)) mDayPicker!!.selectedItem!! else mSelectedDate!![Calendar.DAY_OF_MONTH]
|
||||||
|
if (isStart) {
|
||||||
|
year == mStartYear && month == mStartMonth && day == mStartDay
|
||||||
|
} else {
|
||||||
|
year == mEndYear && month == mEndMonth && day == mEndDay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetMinuteAdapter(isInit: Boolean) {
|
||||||
|
if (hasType(TYPE_MINUTE)) {
|
||||||
|
val isSameStartDay: Boolean
|
||||||
|
val isSameEndDay: Boolean
|
||||||
|
if (hasType(TYPE_MIXED_DATE)) {
|
||||||
|
isSameStartDay = DateUtil.isSameDay(selectedDate.time, mStartDate.timeInMillis)
|
||||||
|
isSameEndDay = DateUtil.isSameDay(selectedDate.time, mEndDate.timeInMillis)
|
||||||
|
} else {
|
||||||
|
val year =
|
||||||
|
if (hasType(TYPE_YEAR)) mYearPicker!!.selectedItem!! else mSelectedDate!![Calendar.YEAR]
|
||||||
|
val month =
|
||||||
|
if (hasType(TYPE_MONTH)) mMonthPicker!!.selectedItem!! else mSelectedDate!![Calendar.MONTH] + 1
|
||||||
|
val day =
|
||||||
|
if (hasType(TYPE_DAY)) mDayPicker!!.selectedItem!! else mSelectedDate!![Calendar.DAY_OF_MONTH]
|
||||||
|
isSameStartDay = year == mStartYear && month == mStartMonth && day == mStartDay
|
||||||
|
isSameEndDay = year == mEndYear && month == mEndMonth && day == mEndDay
|
||||||
|
}
|
||||||
|
val hour: Int = if (hasType(TYPE_HOUR)) {
|
||||||
|
if (hasType(TYPE_12_HOUR) && mNoonPicker!!.selectedItem == 1) {
|
||||||
|
mHourPicker!!.selectedItem!! + 12
|
||||||
|
} else {
|
||||||
|
mHourPicker!!.selectedItem!!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mSelectedDate!![Calendar.HOUR_OF_DAY]
|
||||||
|
}
|
||||||
|
val last = if (isInit) mSelectedDate!![Calendar.MINUTE] else getRealMinute(
|
||||||
|
mMinutePicker!!.selectedPosition
|
||||||
|
)
|
||||||
|
val start = if (isSameStartDay && hour == mStartHour) mStartMinute else 0
|
||||||
|
val end = if (isSameEndDay && hour == mEndHour) mEndMinute else 60 - mTimeMinuteOffset
|
||||||
|
mMinutePicker!!.adapter =
|
||||||
|
NumericWheelAdapter(getValidMinuteValue(start), getValidMinuteValue(end))
|
||||||
|
mMinutePicker!!.setSelectedPosition(findPositionByValidTimes(last), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取有效分钟数对应的item的数值
|
||||||
|
private fun getValidMinuteValue(validTimeMinutes: Int): Int {
|
||||||
|
return validTimeMinutes / mTimeMinuteOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过有效分钟数找到在adapter中的position
|
||||||
|
private fun findPositionByValidTimes(validTimeMinutes: Int): Int {
|
||||||
|
val timesValue = getValidMinuteValue(validTimeMinutes)
|
||||||
|
return if (mMinutePicker != null) {
|
||||||
|
timesValue - mMinutePicker!!.adapter!!.getItem(0)!!
|
||||||
|
} else timesValue - mTimePicker!!.adapter!!.getItem(0)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对应position的真实分钟数,注意这里必须使用position
|
||||||
|
*
|
||||||
|
* @param position [BasePickerView.getSelectedPosition]
|
||||||
|
*/
|
||||||
|
// 获指定position的分钟item对应的真实的分钟数
|
||||||
|
private fun getRealMinute(position: Int): Int {
|
||||||
|
// bugfix:这个position是下标,要拿对应item的数值来计算
|
||||||
|
return mMinutePicker!!.adapter!!.getItem(position)!! * mTimeMinuteOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取指定position对应的有效的分钟数
|
||||||
|
private fun getPositionValidMinutes(position: Int): Int {
|
||||||
|
return mTimePicker!!.adapter!!.getItem(position)!! * mTimeMinuteOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取有效分钟数对应的item的数值
|
||||||
|
private fun getValidTimesValue(validTimeMinutes: Int): Int {
|
||||||
|
return validTimeMinutes / mTimeMinuteOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取根据mTimeMinuteOffset处理后的有效分钟数
|
||||||
|
* 默认为 start <= X <= end 即都不包含在内
|
||||||
|
*/
|
||||||
|
private fun getValidTimeMinutes(timeMinutes: Int, isStart: Boolean): Int {
|
||||||
|
var validTimeMinutes: Int
|
||||||
|
val offset = timeMinutes % mTimeMinuteOffset
|
||||||
|
if (offset == 0) {
|
||||||
|
validTimeMinutes = timeMinutes
|
||||||
|
} else {
|
||||||
|
if (isStart) {
|
||||||
|
validTimeMinutes = timeMinutes - offset
|
||||||
|
if (!mContainsStarDate) {
|
||||||
|
validTimeMinutes += mTimeMinuteOffset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
validTimeMinutes = timeMinutes - offset
|
||||||
|
if (mContainsEndDate) {
|
||||||
|
validTimeMinutes += mTimeMinuteOffset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validTimeMinutes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取时间的分钟数
|
||||||
|
*/
|
||||||
|
private fun getValidTimeMinutes(calendar: Calendar?, isStart: Boolean): Int {
|
||||||
|
if (calendar == null) return 0
|
||||||
|
val hour = calendar[Calendar.HOUR_OF_DAY]
|
||||||
|
val minute = calendar[Calendar.MINUTE]
|
||||||
|
val minutes = hour * 60 + minute
|
||||||
|
return getValidTimeMinutes(minutes, isStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定日期距离第0个的offset
|
||||||
|
*/
|
||||||
|
private fun offsetStart(calendar: Calendar): Int {
|
||||||
|
return DateUtil.getIntervalDay(mStartDate.timeInMillis, calendar.timeInMillis)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val selectedDate: Date
|
||||||
|
get() = getPositionDate(mDatePicker!!.selectedPosition)
|
||||||
|
|
||||||
|
// 获取对应position的日期
|
||||||
|
private fun getPositionDate(position: Int): Date {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.timeInMillis = mStartDate.timeInMillis
|
||||||
|
calendar.add(Calendar.DAY_OF_YEAR, position)
|
||||||
|
return calendar.time
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取对应position的时间
|
||||||
|
private fun getPositionTime(position: Int): Date {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
// 计算出position对应的hour & minute
|
||||||
|
val minutes = mTimePicker!!.adapter!!.getItem(position)!! * mTimeMinuteOffset
|
||||||
|
val hour = minutes / 60
|
||||||
|
val minute = minutes % 60
|
||||||
|
calendar[Calendar.HOUR_OF_DAY] = hour
|
||||||
|
calendar[Calendar.MINUTE] = minute
|
||||||
|
return calendar.time
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSelected(pickerView: BasePickerView<*>, position: Int) {
|
||||||
|
// 联动,年份、月份是固定的,使用日历,获取指定指定某年某月的日期
|
||||||
|
when (pickerView.tag as Int) {
|
||||||
|
TYPE_YEAR -> resetMonthAdapter(false)
|
||||||
|
TYPE_MONTH -> resetDayAdapter(false)
|
||||||
|
TYPE_MIXED_DATE, TYPE_DAY -> resetNoonAdapter(false)
|
||||||
|
TYPE_12_HOUR -> if (hasType(TYPE_MIXED_TIME)) {
|
||||||
|
resetTimeAdapter(false)
|
||||||
|
} else {
|
||||||
|
resetHourAdapter(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_HOUR -> resetMinuteAdapter(false)
|
||||||
|
}
|
||||||
|
if (!needDialog){
|
||||||
|
onConfirm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfirm() {
|
||||||
|
onTimeSelectListener.onTimeSelect(this, selectedDates)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param position 这个是adapter的position,但是起始时间如果不从0开始就不对了
|
||||||
|
*/
|
||||||
|
override fun format(
|
||||||
|
pickerView: BasePickerView<*>, position: Int, charSequence: CharSequence?
|
||||||
|
): CharSequence? {
|
||||||
|
if (formatter == null) return charSequence
|
||||||
|
val type = pickerView.tag as Int
|
||||||
|
val value: Long = when (type) {
|
||||||
|
TYPE_MIXED_DATE -> {
|
||||||
|
getPositionDate(position).time
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_MIXED_TIME -> {
|
||||||
|
getPositionTime(position).time
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_MINUTE -> {
|
||||||
|
getRealMinute(position).toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
charSequence.toString().toInt().toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return formatter!!.format(this, type, position, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制设置的属性直接在构造方法中设置
|
||||||
|
*
|
||||||
|
* @param listener listener
|
||||||
|
*/
|
||||||
|
class Builder(
|
||||||
|
private val context: Context,
|
||||||
|
private val type: Int,
|
||||||
|
private val onTimeSelectListener: OnTimeSelectListener
|
||||||
|
) {
|
||||||
|
// 都应该设置起止时间的,哪怕是只有时间格式,因为真实回调的是时间戳
|
||||||
|
private var mStartDate: Long = 0 // 默认起始为1970/1/1 8:0:0
|
||||||
|
private var mEndDate = 4133865600000L // 默认截止为2100/12/31 0:0:0
|
||||||
|
private var mSelectedDate: Long = -1
|
||||||
|
private var mFormatter: Formatter? = null
|
||||||
|
private var mInterceptor: Interceptor? = null
|
||||||
|
|
||||||
|
// 时间分钟间隔
|
||||||
|
private var mTimeMinuteOffset = 1
|
||||||
|
|
||||||
|
// 设置mTimeMinuteOffset时,是否包含起止时间
|
||||||
|
private var mContainsStarDate = false
|
||||||
|
private var mContainsEndDate = false
|
||||||
|
private var needDialog = true
|
||||||
|
private var iPickerDialog: IPickerDialog? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置起止时间
|
||||||
|
*
|
||||||
|
* @param startDate 起始时间
|
||||||
|
* @param endDate 截止时间
|
||||||
|
*/
|
||||||
|
fun setRangDate(startDate: Long, endDate: Long): Builder {
|
||||||
|
mEndDate = endDate
|
||||||
|
mStartDate = if (endDate < startDate) {
|
||||||
|
endDate
|
||||||
|
} else {
|
||||||
|
startDate
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置选中时间戳
|
||||||
|
*
|
||||||
|
* @param millis 选中时间戳
|
||||||
|
*/
|
||||||
|
fun setSelectedDate(millis: Long): Builder {
|
||||||
|
mSelectedDate = millis
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置时间间隔分钟数,以0为起始边界
|
||||||
|
*
|
||||||
|
* @param timeMinuteOffset 60%offset==0才有效
|
||||||
|
*/
|
||||||
|
fun setTimeMinuteOffset(timeMinuteOffset: Int): Builder {
|
||||||
|
mTimeMinuteOffset = timeMinuteOffset
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置mTimeMinuteOffset作用时,是否包含超出的startDate
|
||||||
|
*
|
||||||
|
* @param containsStarDate 是否包含startDate
|
||||||
|
*/
|
||||||
|
fun setContainsStarDate(containsStarDate: Boolean): Builder {
|
||||||
|
mContainsStarDate = containsStarDate
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置mTimeMinuteOffset作用时,是否包含超出的endDate
|
||||||
|
*
|
||||||
|
* @param containsEndDate 是否包含endDate
|
||||||
|
*/
|
||||||
|
fun setContainsEndDate(containsEndDate: Boolean): Builder {
|
||||||
|
mContainsEndDate = containsEndDate
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFormatter(formatter: Formatter?): Builder {
|
||||||
|
mFormatter = formatter
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setInterceptor(interceptor: Interceptor?): Builder {
|
||||||
|
mInterceptor = interceptor
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义弹窗
|
||||||
|
*
|
||||||
|
* @param iPickerDialog 如果为null表示不需要弹窗
|
||||||
|
*/
|
||||||
|
fun dialog(iPickerDialog: IPickerDialog?): Builder {
|
||||||
|
needDialog = iPickerDialog != null
|
||||||
|
this.iPickerDialog = iPickerDialog
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(): TimePicker {
|
||||||
|
val picker = TimePicker(context, type, onTimeSelectListener)
|
||||||
|
// 不支持重复设置的,都在builder中控制,一次性行为
|
||||||
|
picker.needDialog = needDialog
|
||||||
|
picker.iPickerDialog = iPickerDialog
|
||||||
|
picker.initPickerView()
|
||||||
|
picker.setInterceptor(mInterceptor)
|
||||||
|
picker.mTimeMinuteOffset = mTimeMinuteOffset
|
||||||
|
picker.mContainsStarDate = mContainsStarDate
|
||||||
|
picker.mContainsEndDate = mContainsEndDate
|
||||||
|
picker.setRangDate(mStartDate, mEndDate)
|
||||||
|
if (mFormatter == null) {
|
||||||
|
mFormatter = DefaultFormatter()
|
||||||
|
}
|
||||||
|
picker.formatter = mFormatter
|
||||||
|
picker.initPicker()
|
||||||
|
if (mSelectedDate < 0) {
|
||||||
|
picker.reset()
|
||||||
|
} else {
|
||||||
|
picker.setSelectedDate(mSelectedDate)
|
||||||
|
}
|
||||||
|
return picker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class DefaultFormatter : Formatter {
|
||||||
|
override fun format(
|
||||||
|
picker: TimePicker, type: Int, position: Int, value: Long
|
||||||
|
): CharSequence {
|
||||||
|
when (type) {
|
||||||
|
TYPE_YEAR -> {
|
||||||
|
return value.toString() + "年"
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_MONTH -> {
|
||||||
|
return String.format("%02d月", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_DAY -> {
|
||||||
|
return String.format("%02d日", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_12_HOUR -> {
|
||||||
|
return if (value == 0L) "上午" else "下午"
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_HOUR -> {
|
||||||
|
if (picker.hasType(TYPE_12_HOUR)) {
|
||||||
|
if (value == 0L) {
|
||||||
|
return "12时"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String.format("%2d时", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_MINUTE -> {
|
||||||
|
return String.format("%2d分", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_MIXED_DATE -> {
|
||||||
|
// 如果是TYPE_MIXED_,则value表示时间戳
|
||||||
|
return sDefaultDateFormat.format(Date(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_MIXED_TIME -> {
|
||||||
|
val time = sDefaultTimeFormat.format(Date(value))
|
||||||
|
return if (picker.hasType(TYPE_12_HOUR)) {
|
||||||
|
time.replace("00:", "12:") // 12小时
|
||||||
|
} else {
|
||||||
|
time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> return value.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface Formatter {
|
||||||
|
/**
|
||||||
|
* 根据type和num格式化时间
|
||||||
|
*
|
||||||
|
* @param picker picker
|
||||||
|
* @param type 并不是模式,而是当前item所属的type,如年,时
|
||||||
|
* @param position position
|
||||||
|
* @param value position item对应的value,如果是TYPE_MIXED_DATE表示日期时间戳,否则表示显示的数字
|
||||||
|
*/
|
||||||
|
fun format(
|
||||||
|
picker: TimePicker, type: Int, position: Int, value: Long
|
||||||
|
): CharSequence
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface OnTimeSelectListener {
|
||||||
|
/**
|
||||||
|
* 点击确定按钮选择时间后回调
|
||||||
|
*
|
||||||
|
* @param date 选择的时间
|
||||||
|
*/
|
||||||
|
fun onTimeSelect(picker: TimePicker, date: Date)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TYPE_YEAR = 0x01
|
||||||
|
const val TYPE_MONTH = 0x02
|
||||||
|
const val TYPE_DAY = 0x04
|
||||||
|
const val TYPE_HOUR = 0x08
|
||||||
|
const val TYPE_MINUTE = 0x10
|
||||||
|
|
||||||
|
/** 日期聚合 */
|
||||||
|
const val TYPE_MIXED_DATE = 0x20
|
||||||
|
|
||||||
|
/** 时间聚合 */
|
||||||
|
const val TYPE_MIXED_TIME = 0x40
|
||||||
|
|
||||||
|
/** 上午、下午(12小时制,默认24小时制,不显示上午,下午) */
|
||||||
|
const val TYPE_12_HOUR = 0x80
|
||||||
|
|
||||||
|
// 日期:年月日
|
||||||
|
const val TYPE_DATE = TYPE_YEAR or TYPE_MONTH or TYPE_DAY
|
||||||
|
|
||||||
|
// 时间:小时、分钟
|
||||||
|
const val TYPE_TIME = TYPE_HOUR or TYPE_MINUTE
|
||||||
|
|
||||||
|
// 全部
|
||||||
|
const val TYPE_ALL = TYPE_DATE or TYPE_TIME
|
||||||
|
|
||||||
|
var sDefaultDateFormat: DateFormat = createDateFormat("yyyy年MM月dd日")
|
||||||
|
var sDefaultTimeFormat: DateFormat = createDateFormat("HH:mm")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
package org.jaaksi.pickerview.picker.option
|
||||||
|
|
||||||
|
import org.jaaksi.pickerview.adapter.ArrayWheelAdapter
|
||||||
|
import org.jaaksi.pickerview.dataset.OptionDataSet
|
||||||
|
import org.jaaksi.pickerview.picker.OptionPicker
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by fuchaoyang on 2018/7/6.<br></br>
|
||||||
|
* description:无关联的 OptionPicker Delegate
|
||||||
|
*/
|
||||||
|
class ForeignOptionDelegate : IOptionDelegate {
|
||||||
|
private var mDelegate: OptionPicker.Delegate? = null
|
||||||
|
private var mOptions: Array<out List<OptionDataSet>>? = null
|
||||||
|
override fun init(delegate: OptionPicker.Delegate) {
|
||||||
|
mDelegate = delegate
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setData(vararg options: List<OptionDataSet>) {
|
||||||
|
mOptions = options
|
||||||
|
for (i in 0 until mDelegate!!.hierarchy) {
|
||||||
|
val pickerView = mDelegate!!.getPickerViews()[i]
|
||||||
|
pickerView.adapter = ArrayWheelAdapter(mOptions!![i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setSelectedWithValues(vararg values: String?) {
|
||||||
|
for (i in 0 until mDelegate!!.hierarchy) {
|
||||||
|
if (mOptions == null || mOptions!!.isEmpty()) { // 数据源无效
|
||||||
|
mDelegate!!.selectedPosition[i] = -1
|
||||||
|
} else if (values.size <= i || values[i] == null) { // 选中默认项0...
|
||||||
|
mDelegate!!.selectedPosition[i] = 0
|
||||||
|
} else {
|
||||||
|
val options = mOptions!![i]
|
||||||
|
for (j in 0..options.size) {
|
||||||
|
// 遍历找到选中的下标,如果没有找到,则将下标置为0
|
||||||
|
if (j == options.size) {
|
||||||
|
mDelegate!!.selectedPosition[i] = 0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (values[i] == options[j].getValue()) {
|
||||||
|
mDelegate!!.selectedPosition[i] = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mDelegate!!.selectedPosition[i] != -1) {
|
||||||
|
mDelegate!!.getPickerViews()[i]
|
||||||
|
.setSelectedPosition(mDelegate!!.selectedPosition[i], false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val selectedOptions: Array<OptionDataSet?>
|
||||||
|
get() {
|
||||||
|
val optionDataSets = arrayOfNulls<OptionDataSet>(
|
||||||
|
mDelegate!!.hierarchy
|
||||||
|
)
|
||||||
|
for (i in 0 until mDelegate!!.hierarchy) {
|
||||||
|
val selectedPosition = mDelegate!!.selectedPosition[i]
|
||||||
|
if (selectedPosition == -1) break
|
||||||
|
optionDataSets[i] = mOptions!![i]!![selectedPosition]
|
||||||
|
}
|
||||||
|
return optionDataSets
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset() {
|
||||||
|
for (i in 0 until mDelegate!!.hierarchy) {
|
||||||
|
val pickerView = mDelegate!!.getPickerViews()[i]
|
||||||
|
pickerView.setSelectedPosition(mDelegate!!.selectedPosition[i], false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package org.jaaksi.pickerview.picker.option
|
||||||
|
|
||||||
|
import org.jaaksi.pickerview.dataset.OptionDataSet
|
||||||
|
import org.jaaksi.pickerview.picker.OptionPicker
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by fuchaoyang on 2018/7/6.<br></br>
|
||||||
|
* description:
|
||||||
|
*/
|
||||||
|
interface IOptionDelegate {
|
||||||
|
fun init(delegate: OptionPicker.Delegate)
|
||||||
|
|
||||||
|
fun setData(vararg options: List<OptionDataSet>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据选中的values初始化选中的position
|
||||||
|
*/
|
||||||
|
fun setSelectedWithValues(vararg values: String?)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取选中的选项,如果指定index为null则表示该列没有数据
|
||||||
|
*/
|
||||||
|
val selectedOptions: Array<OptionDataSet?>
|
||||||
|
fun reset()
|
||||||
|
}
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
package org.jaaksi.pickerview.picker.option
|
||||||
|
|
||||||
|
import org.jaaksi.pickerview.adapter.ArrayWheelAdapter
|
||||||
|
import org.jaaksi.pickerview.dataset.OptionDataSet
|
||||||
|
import org.jaaksi.pickerview.picker.OptionPicker
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by fuchaoyang on 2018/7/6.<br></br>
|
||||||
|
* description:关联的Option Picker Delegate
|
||||||
|
*/
|
||||||
|
class OptionDelegate : IOptionDelegate {
|
||||||
|
private var mDelegate: OptionPicker.Delegate? = null
|
||||||
|
private var mOptions: List<OptionDataSet>? = null
|
||||||
|
|
||||||
|
override fun init(delegate: OptionPicker.Delegate) {
|
||||||
|
mDelegate = delegate
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setData(vararg options: List<OptionDataSet>) {
|
||||||
|
mOptions = options[0]
|
||||||
|
setSelectedWithValues()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据选中的values初始化选中的position
|
||||||
|
*
|
||||||
|
* @param values 选中数据的value[OptionDataSet.getValue],如果values[i]==null,如果该列有数据,则进行默认选中,否则认为没有该列
|
||||||
|
*/
|
||||||
|
override fun setSelectedWithValues(vararg values: String?) {
|
||||||
|
var temp = mOptions
|
||||||
|
for (i in 0 until mDelegate!!.hierarchy) {
|
||||||
|
val pickerView = mDelegate!!.getPickerViews()[i]
|
||||||
|
val adapter = pickerView.adapter as ArrayWheelAdapter<*>?
|
||||||
|
if (adapter == null || adapter.data !== temp) {
|
||||||
|
pickerView.adapter = ArrayWheelAdapter(temp)
|
||||||
|
}
|
||||||
|
if (temp == null || temp.size == 0) { // 数据源无效
|
||||||
|
mDelegate!!.selectedPosition[i] = -1
|
||||||
|
} else if (values.size <= i || values[i] == null) { // 选中默认项0...
|
||||||
|
mDelegate!!.selectedPosition[i] = 0
|
||||||
|
} else { // 遍历找到选中的下标,如果没有找到,则将下标置为0
|
||||||
|
for (j in temp.indices) {
|
||||||
|
val dataSet = temp[j]
|
||||||
|
if (values[i] == dataSet.getValue()) {
|
||||||
|
mDelegate!!.selectedPosition[i] = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (j == temp.size) {
|
||||||
|
mDelegate!!.selectedPosition[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mDelegate!!.selectedPosition[i] == -1) {
|
||||||
|
temp = null
|
||||||
|
} else {
|
||||||
|
pickerView.setSelectedPosition(mDelegate!!.selectedPosition[i], false)
|
||||||
|
val dataSet = temp!![mDelegate!!.selectedPosition[i]]
|
||||||
|
temp = dataSet.getSubs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset() {
|
||||||
|
var temp = mOptions
|
||||||
|
for (i in mDelegate!!.getPickerViews().indices) {
|
||||||
|
val pickerView = mDelegate!!.getPickerViews()[i]
|
||||||
|
val adapter = pickerView.adapter as ArrayWheelAdapter<*>?
|
||||||
|
if (adapter == null || adapter.data !== temp) {
|
||||||
|
pickerView.adapter = ArrayWheelAdapter(temp)
|
||||||
|
}
|
||||||
|
// 重置下标
|
||||||
|
pickerView.setSelectedPosition(mDelegate!!.selectedPosition[i], false)
|
||||||
|
if (temp.isNullOrEmpty()) {
|
||||||
|
mDelegate!!.selectedPosition[i] = -1 // 下标置为-1表示选中的第i列没有
|
||||||
|
} else if (temp.size <= mDelegate!!.selectedPosition[i]) { // 下标超过范围,取默认值0
|
||||||
|
mDelegate!!.selectedPosition[i] = 0
|
||||||
|
}
|
||||||
|
if (mDelegate!!.selectedPosition[i] == -1) {
|
||||||
|
temp = null
|
||||||
|
} else {
|
||||||
|
val dataSet = temp!![mDelegate!!.selectedPosition[i]]
|
||||||
|
temp = dataSet.getSubs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val selectedOptions: Array<OptionDataSet?>
|
||||||
|
/**
|
||||||
|
* 获取选中的选项
|
||||||
|
*
|
||||||
|
* @return 选中的选项,如果指定index为null则表示该列没有数据
|
||||||
|
*/
|
||||||
|
get() {
|
||||||
|
val optionDataSets = arrayOfNulls<OptionDataSet>(
|
||||||
|
mDelegate!!.hierarchy
|
||||||
|
)
|
||||||
|
var temp = mOptions
|
||||||
|
for (i in 0 until mDelegate!!.hierarchy) {
|
||||||
|
if (mDelegate!!.selectedPosition[i] == -1) break
|
||||||
|
// !=-1则一定会有数据,所以不需要判断temp是否为空,也不用担心会下标越界
|
||||||
|
optionDataSets[i] = temp!![mDelegate!!.selectedPosition[i]]
|
||||||
|
temp = optionDataSets[i]!!.getSubs()
|
||||||
|
}
|
||||||
|
return optionDataSets
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
package org.jaaksi.pickerview.util
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间:2018年02月02日12:00 <br></br>
|
||||||
|
* 作者:fuchaoyang <br></br>
|
||||||
|
* 描述:时间工具类
|
||||||
|
*/
|
||||||
|
object DateUtil {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun createDateFormat(format: String): SimpleDateFormat {
|
||||||
|
return SimpleDateFormat(format, Locale.getDefault())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取某年某月有多少天
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun getDayOfMonth(year: Int, month: Int): Int {
|
||||||
|
val c = Calendar.getInstance()
|
||||||
|
c[year, month] = 0 //输入类型为int类型
|
||||||
|
return c[Calendar.DAY_OF_MONTH]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断两个时间戳是否是同一天
|
||||||
|
fun isSameDay(time1: Long, time2: Long): Boolean{
|
||||||
|
return time1.dayStart() == time2.dayStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不能用时间戳差值 / 86400000, 夏令时会有误差
|
||||||
|
* @return endTime - startTime 相差的天数
|
||||||
|
*/
|
||||||
|
fun getIntervalDay(time1: Long, time2: Long): Int {
|
||||||
|
val cal1 = Calendar.getInstance().apply { timeInMillis = min(time1,time2) }
|
||||||
|
val cal2 = Calendar.getInstance().apply { timeInMillis = max(time1, time2) }
|
||||||
|
|
||||||
|
// 如果是同一年,直接计算 dayOfYear 差值
|
||||||
|
if (cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)) {
|
||||||
|
return if (time1 > time2)
|
||||||
|
-(cal2.get(Calendar.DAY_OF_YEAR) - cal1.get(Calendar.DAY_OF_YEAR))
|
||||||
|
else
|
||||||
|
cal2.get(Calendar.DAY_OF_YEAR) - cal1.get(Calendar.DAY_OF_YEAR)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跨年计算
|
||||||
|
var daysBetween = 0
|
||||||
|
|
||||||
|
// 1. 计算起始年剩余的天数
|
||||||
|
val daysLeftInYear1 = cal1.getActualMaximum(Calendar.DAY_OF_YEAR) - cal1.get(Calendar.DAY_OF_YEAR)
|
||||||
|
daysBetween += daysLeftInYear1
|
||||||
|
|
||||||
|
// 2. 计算中间完整年份的天数
|
||||||
|
var year = cal1.get(Calendar.YEAR) + 1
|
||||||
|
while (year < cal2.get(Calendar.YEAR)) {
|
||||||
|
val tempCal = Calendar.getInstance().apply { set(Calendar.YEAR, year) }
|
||||||
|
daysBetween += tempCal.getActualMaximum(Calendar.DAY_OF_YEAR)
|
||||||
|
year++
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 计算结束年已过的天数
|
||||||
|
daysBetween += cal2.get(Calendar.DAY_OF_YEAR)
|
||||||
|
|
||||||
|
return if (time1 > time2) -daysBetween else daysBetween
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Long.toCalendar(): Calendar {
|
||||||
|
return Calendar.getInstance().apply { timeInMillis = this@toCalendar }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Long.dayStart(): Long {
|
||||||
|
val calendar = this.toCalendar()
|
||||||
|
calendar[Calendar.HOUR_OF_DAY] = 0
|
||||||
|
calendar[Calendar.MINUTE] = 0
|
||||||
|
calendar[Calendar.SECOND] = 0
|
||||||
|
calendar[Calendar.MILLISECOND] = 0
|
||||||
|
return calendar.timeInMillis
|
||||||
|
}
|
||||||
|
}
|
||||||
48
pickerview/src/main/java/org/jaaksi/pickerview/util/Util.kt
Normal file
48
pickerview/src/main/java/org/jaaksi/pickerview/util/Util.kt
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package org.jaaksi.pickerview.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
|
||||||
|
object Util {
|
||||||
|
/**
|
||||||
|
* dip转换px
|
||||||
|
*
|
||||||
|
* @param context 上下文
|
||||||
|
* @param dpValue dip值
|
||||||
|
* @return px值
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun dip2px(context: Context, dpValue: Float): Int {
|
||||||
|
val scale = context.resources.displayMetrics.density
|
||||||
|
return (dpValue * scale + 0.5f).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算渐变后的颜色
|
||||||
|
*
|
||||||
|
* @param startColor 开始颜色
|
||||||
|
* @param endColor 结束颜色
|
||||||
|
* @param rate 渐变率(0,1)
|
||||||
|
* @return 渐变后的颜色,当rate=0时,返回startColor,当rate=1时返回endColor
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun computeGradientColor(startColor: Int, endColor: Int, rate: Float): Int {
|
||||||
|
var rate = rate
|
||||||
|
if (rate < 0) {
|
||||||
|
rate = 0f
|
||||||
|
}
|
||||||
|
if (rate > 1) {
|
||||||
|
rate = 1f
|
||||||
|
}
|
||||||
|
val alpha = Color.alpha(endColor) - Color.alpha(startColor)
|
||||||
|
val red = Color.red(endColor) - Color.red(startColor)
|
||||||
|
val green = Color.green(endColor) - Color.green(startColor)
|
||||||
|
val blue = Color.blue(endColor) - Color.blue(startColor)
|
||||||
|
return Color.argb(
|
||||||
|
Math.round(Color.alpha(startColor) + alpha * rate),
|
||||||
|
Math.round(Color.red(startColor) + red * rate),
|
||||||
|
Math.round(Color.green(startColor) + green * rate),
|
||||||
|
Math.round(Color.blue(startColor) + blue * rate)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,157 @@
|
|||||||
|
package org.jaaksi.pickerview.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.core.graphics.toColorInt
|
||||||
|
import org.jaaksi.pickerview.util.Util.dip2px
|
||||||
|
import org.jaaksi.pickerview.widget.BasePickerView.CenterDecoration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间:2018年02月17日10:55 <br></br>
|
||||||
|
* 作者:fuchaoyang <br></br>
|
||||||
|
* 描述:default centerdecoration
|
||||||
|
* 样式:背景图,上下两条线,支持设置线条颜色,宽度,margin
|
||||||
|
*/
|
||||||
|
class DefaultCenterDecoration(private val mContext: Context) : CenterDecoration {
|
||||||
|
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||||
|
private var mDrawable: Drawable? = null
|
||||||
|
private var marginRect: Rect? = null
|
||||||
|
private val mRect = Rect()
|
||||||
|
|
||||||
|
init {
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
setLineWidth(sDefaultLineWidth)
|
||||||
|
setLineColor(sDefaultLineColor)
|
||||||
|
setDrawable(sDefaultDrawable)
|
||||||
|
setMargin(sDefaultMarginRect)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置linecolor
|
||||||
|
*
|
||||||
|
* @param lineColor line color 如果设置为Color.TRANSPARENT就不绘制线
|
||||||
|
*/
|
||||||
|
fun setLineColor(@ColorInt lineColor: Int): DefaultCenterDecoration {
|
||||||
|
paint.color = lineColor
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置装饰线宽度
|
||||||
|
*
|
||||||
|
* @param lineWidth 装饰线宽度 单位dp
|
||||||
|
*/
|
||||||
|
fun setLineWidth(lineWidth: Float): DefaultCenterDecoration {
|
||||||
|
paint.strokeWidth = dip2px(mContext, lineWidth).toFloat()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置CenterDecoration drawable
|
||||||
|
*/
|
||||||
|
fun setDrawable(drawable: Drawable?): DefaultCenterDecoration {
|
||||||
|
mDrawable = drawable
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDrawable(@ColorInt color: Int): DefaultCenterDecoration {
|
||||||
|
mDrawable = ColorDrawable(color)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置装饰线的margin 单位px
|
||||||
|
* 水平方向认为。left=topmargin,top为rightmargin,right=botommargin,botom=leftmargin
|
||||||
|
*/
|
||||||
|
fun setMargin(left: Int, top: Int, right: Int, bottom: Int): DefaultCenterDecoration {
|
||||||
|
marginRect = Rect(left, top, right, bottom)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置装饰线的margin
|
||||||
|
* 水平方向认为。left=topmargin,top为rightmargin,right=botommargin,botom=leftmargin
|
||||||
|
*/
|
||||||
|
fun setMargin(marginRect: Rect?): DefaultCenterDecoration {
|
||||||
|
this.marginRect = marginRect
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun drawIndicator(
|
||||||
|
pickerView: BasePickerView<*>,
|
||||||
|
canvas: Canvas,
|
||||||
|
left: Int,
|
||||||
|
top: Int,
|
||||||
|
right: Int,
|
||||||
|
bottom: Int
|
||||||
|
) {
|
||||||
|
if (marginRect == null) {
|
||||||
|
marginRect = Rect()
|
||||||
|
}
|
||||||
|
val isHorizontal = pickerView.isHorizontal
|
||||||
|
if (mDrawable != null) {
|
||||||
|
if (!isHorizontal) {
|
||||||
|
mRect[left + marginRect!!.left, top + marginRect!!.top + (paint.strokeWidth / 2).toInt(), right - marginRect!!.right] =
|
||||||
|
bottom - marginRect!!.bottom - (paint.strokeWidth / 2).toInt()
|
||||||
|
} else {
|
||||||
|
mRect[left + marginRect!!.top + (paint.strokeWidth / 2).toInt(), top + marginRect!!.right, right - marginRect!!.bottom - (paint.strokeWidth / 2).toInt()] =
|
||||||
|
bottom - marginRect!!.left
|
||||||
|
}
|
||||||
|
mDrawable!!.bounds = mRect
|
||||||
|
mDrawable!!.draw(canvas)
|
||||||
|
}
|
||||||
|
if (paint.color == Color.TRANSPARENT) return
|
||||||
|
if (!isHorizontal) {
|
||||||
|
canvas.drawLine(
|
||||||
|
(left + marginRect!!.left).toFloat(),
|
||||||
|
(top + marginRect!!.top).toFloat(),
|
||||||
|
(right - marginRect!!.right).toFloat(),
|
||||||
|
(top + marginRect!!.top).toFloat(),
|
||||||
|
paint
|
||||||
|
)
|
||||||
|
canvas.drawLine(
|
||||||
|
(left + marginRect!!.left).toFloat(),
|
||||||
|
(bottom - marginRect!!.bottom).toFloat(),
|
||||||
|
(right - marginRect!!.right).toFloat(),
|
||||||
|
(bottom - marginRect!!.bottom).toFloat(),
|
||||||
|
paint
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// 水平方向认为。left=topmargin,top为rightmargin,right=botommargin,botom=leftmargin
|
||||||
|
canvas.drawLine(
|
||||||
|
(left + marginRect!!.top).toFloat(),
|
||||||
|
(top + marginRect!!.right).toFloat(),
|
||||||
|
(left + marginRect!!.top).toFloat(),
|
||||||
|
(bottom - marginRect!!.left).toFloat(),
|
||||||
|
paint
|
||||||
|
)
|
||||||
|
canvas.drawLine(
|
||||||
|
(right - marginRect!!.bottom).toFloat(),
|
||||||
|
(top + marginRect!!.right).toFloat(),
|
||||||
|
(right - marginRect!!.bottom).toFloat(),
|
||||||
|
(bottom - marginRect!!.left).toFloat(),
|
||||||
|
paint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** default line color */
|
||||||
|
var sDefaultLineColor = "#ECECEE".toColorInt()
|
||||||
|
|
||||||
|
/** default line width */
|
||||||
|
var sDefaultLineWidth = 1f
|
||||||
|
|
||||||
|
/** default item background drawable */
|
||||||
|
var sDefaultDrawable: Drawable? = null
|
||||||
|
|
||||||
|
/** default line margin */
|
||||||
|
var sDefaultMarginRect: Rect? = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,267 @@
|
|||||||
|
package org.jaaksi.pickerview.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.text.Layout
|
||||||
|
import android.text.StaticLayout
|
||||||
|
import android.text.TextPaint
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.core.graphics.toColorInt
|
||||||
|
import org.jaaksi.pickerview.R
|
||||||
|
import org.jaaksi.pickerview.dataset.PickerDataSet
|
||||||
|
import org.jaaksi.pickerview.util.Util.computeGradientColor
|
||||||
|
import org.jaaksi.pickerview.util.Util.dip2px
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串滚动选择器
|
||||||
|
* https://github.com/1993hzw/Androids/blob/master/androids/src/cn/forward/androids/views/StringScrollPicker.java
|
||||||
|
* 做以下修改:
|
||||||
|
* 1.数据不仅仅支持String,支持任意数据。方便设置数据
|
||||||
|
* 2.绘制文字不使用StaticLayout,只绘制一行,且居中
|
||||||
|
*
|
||||||
|
* 其实View不关心泛型,需要关心的是Adapter,但是我们的adapter是通用的,并不需要用户再定义,所以无法指明泛型,所以不得以view这里依然用泛型。
|
||||||
|
* 这里为了兼容支持直接设置String,int数据
|
||||||
|
*
|
||||||
|
* @see PickerDataSet 如果是自定义数据类型,请实现该接口
|
||||||
|
*/
|
||||||
|
class PickerView<T> @JvmOverloads constructor(
|
||||||
|
context: Context?,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : BasePickerView<T>(context, attrs, defStyleAttr) {
|
||||||
|
private val mPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
|
||||||
|
private var outTextSize = 0 // 最小的字体
|
||||||
|
private var centerTextSize = 0 // 最大的字体
|
||||||
|
|
||||||
|
// 字体渐变颜色
|
||||||
|
private var centerColor = sCenterColor // 中间选中item的颜色
|
||||||
|
private var outColor = sOutColor // 上下两边的颜色
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置对其方式
|
||||||
|
*
|
||||||
|
* @param alignment 对齐方式
|
||||||
|
*/
|
||||||
|
var alignment = Layout.Alignment.ALIGN_CENTER // 对齐方式,默认居中
|
||||||
|
private var mShadowColors: IntArray? = sShadowColors
|
||||||
|
|
||||||
|
// Shadows drawables
|
||||||
|
private var mStartShadow: GradientDrawable? = null
|
||||||
|
private var mEndShadow: GradientDrawable? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
mPaint.style = Paint.Style.FILL
|
||||||
|
mPaint.color = Color.BLACK
|
||||||
|
init(attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFont(tf: Typeface){
|
||||||
|
mPaint.typeface = tf
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init(attrs: AttributeSet?) {
|
||||||
|
if (attrs != null) {
|
||||||
|
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.PickerView)
|
||||||
|
outTextSize =
|
||||||
|
typedArray.getDimensionPixelSize(R.styleable.PickerView_pv_out_text_size, 0)
|
||||||
|
centerTextSize =
|
||||||
|
typedArray.getDimensionPixelSize(R.styleable.PickerView_pv_center_text_size, 0)
|
||||||
|
centerColor = typedArray.getColor(R.styleable.PickerView_pv_start_color, centerColor)
|
||||||
|
outColor = typedArray.getColor(R.styleable.PickerView_pv_end_color, outColor)
|
||||||
|
val align = typedArray.getInt(R.styleable.PickerView_pv_alignment, 1)
|
||||||
|
if (align == 2) {
|
||||||
|
alignment = Layout.Alignment.ALIGN_NORMAL
|
||||||
|
} else if (align == 3) {
|
||||||
|
alignment = Layout.Alignment.ALIGN_OPPOSITE
|
||||||
|
} else {
|
||||||
|
alignment = Layout.Alignment.ALIGN_CENTER
|
||||||
|
}
|
||||||
|
typedArray.recycle()
|
||||||
|
}
|
||||||
|
if (outTextSize <= 0) {
|
||||||
|
outTextSize = dip2px(context, sOutTextSize.toFloat())
|
||||||
|
}
|
||||||
|
if (centerTextSize <= 0) {
|
||||||
|
centerTextSize = dip2px(context, sCenterTextSize.toFloat())
|
||||||
|
}
|
||||||
|
resetShadow()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetShadow() {
|
||||||
|
if (mShadowColors == null) {
|
||||||
|
mStartShadow = null
|
||||||
|
mEndShadow = null
|
||||||
|
} else {
|
||||||
|
if (isHorizontal) {
|
||||||
|
mStartShadow =
|
||||||
|
GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, mShadowColors)
|
||||||
|
mEndShadow =
|
||||||
|
GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, mShadowColors)
|
||||||
|
} else {
|
||||||
|
mStartShadow =
|
||||||
|
GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, mShadowColors)
|
||||||
|
mEndShadow =
|
||||||
|
GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, mShadowColors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置蒙版
|
||||||
|
*/
|
||||||
|
fun setShadowsColors(@ColorInt colors: IntArray?) {
|
||||||
|
mShadowColors = colors
|
||||||
|
resetShadow()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置center out 文字 color
|
||||||
|
*
|
||||||
|
* @param centerColor 正中间的颜色
|
||||||
|
* @param outColor 上下两边的颜色
|
||||||
|
*/
|
||||||
|
fun setColor(@ColorInt centerColor: Int, @ColorInt outColor: Int) {
|
||||||
|
this.centerColor = centerColor
|
||||||
|
this.outColor = outColor
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置item文字大小,单位dp
|
||||||
|
*
|
||||||
|
* @param minText 沒有被选中时的最小文字
|
||||||
|
* @param maxText 被选中时的最大文字
|
||||||
|
*/
|
||||||
|
fun setTextSize(minText: Int, maxText: Int) {
|
||||||
|
outTextSize = dip2px(context, minText.toFloat())
|
||||||
|
centerTextSize = dip2px(context, maxText.toFloat())
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
super.onDraw(canvas)
|
||||||
|
if (mShadowColors != null) {
|
||||||
|
drawShadows(canvas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun drawItem(
|
||||||
|
canvas: Canvas?, data: T?, position: Int, relative: Int, moveLength: Float,
|
||||||
|
top: Float
|
||||||
|
) {
|
||||||
|
// 添加一层装饰器
|
||||||
|
var text: CharSequence? = if (data is PickerDataSet) {
|
||||||
|
(data as PickerDataSet).getCharSequence()
|
||||||
|
} else {
|
||||||
|
data.toString()
|
||||||
|
}
|
||||||
|
text = if (formatter == null) text else formatter!!.format(this, position, text)
|
||||||
|
if (text == null) return
|
||||||
|
val itemSize = itemSize
|
||||||
|
|
||||||
|
// 设置文字大小
|
||||||
|
if (relative == -1) { // 上一个
|
||||||
|
if (moveLength < 0) { // 向上滑动
|
||||||
|
mPaint.textSize = outTextSize.toFloat()
|
||||||
|
} else { // 向下滑动
|
||||||
|
mPaint.textSize =
|
||||||
|
outTextSize + (centerTextSize - outTextSize) * moveLength / itemSize
|
||||||
|
}
|
||||||
|
} else if (relative == 0) { // 中间item,当前选中
|
||||||
|
mPaint.textSize = (outTextSize
|
||||||
|
+ (centerTextSize - outTextSize) * (itemSize - Math.abs(moveLength)) / itemSize)
|
||||||
|
} else if (relative == 1) { // 下一个
|
||||||
|
if (moveLength > 0) { // 向下滑动
|
||||||
|
mPaint.textSize = outTextSize.toFloat()
|
||||||
|
} else { // 向上滑动
|
||||||
|
mPaint.textSize =
|
||||||
|
outTextSize + (centerTextSize - outTextSize) * -moveLength / itemSize
|
||||||
|
}
|
||||||
|
} else { // 其他
|
||||||
|
mPaint.textSize = outTextSize.toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不换行
|
||||||
|
val layout = StaticLayout(
|
||||||
|
text, 0, text.length, mPaint, dip2px(
|
||||||
|
context, 1000f
|
||||||
|
), alignment,
|
||||||
|
1.0f, 0.0f, true, null, 0
|
||||||
|
)
|
||||||
|
var x = 0f
|
||||||
|
var y = 0f
|
||||||
|
val lineWidth = layout.width.toFloat()
|
||||||
|
if (isHorizontal) { // 水平滚动
|
||||||
|
x = top + (itemWidth - lineWidth) / 2
|
||||||
|
y = ((itemHeight - layout.height) / 2).toFloat()
|
||||||
|
} else { // 垂直滚动
|
||||||
|
x = (itemWidth - lineWidth) / 2
|
||||||
|
y = top + (itemHeight - layout.height) / 2
|
||||||
|
}
|
||||||
|
// 计算渐变颜色
|
||||||
|
computeColor(relative, itemSize, moveLength)
|
||||||
|
canvas!!.save()
|
||||||
|
canvas.translate(x, y)
|
||||||
|
layout.draw(canvas)
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws shadows on top and bottom of control
|
||||||
|
*/
|
||||||
|
private fun drawShadows(canvas: Canvas) {
|
||||||
|
val height = itemHeight
|
||||||
|
mStartShadow!!.setBounds(0, 0, width, height)
|
||||||
|
mStartShadow!!.draw(canvas)
|
||||||
|
mEndShadow!!.setBounds(0, getHeight() - height, width, getHeight())
|
||||||
|
mEndShadow!!.draw(canvas)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算字体颜色,渐变
|
||||||
|
* 1.中间区域为 centerColor,其他未 outColor 参考AndroidPickers
|
||||||
|
* 2.如果再当前位置松开手后,应该选中的那个item的文字颜色为centerColor,其他为outColor
|
||||||
|
* 把这个做成接口,提供默认实现
|
||||||
|
*
|
||||||
|
* @param relative 相对中间item的位置
|
||||||
|
*/
|
||||||
|
private fun computeColor(relative: Int, itemSize: Int, moveLength: Float) {
|
||||||
|
var color = outColor // 其他默认为 mOutColor
|
||||||
|
if (relative == -1 || relative == 1) { // 上一个或下一个
|
||||||
|
// 处理上一个item且向上滑动 或者 处理下一个item且向下滑动 ,颜色为 mOutColor
|
||||||
|
color = if (relative == -1 && moveLength < 0 || relative == 1 && moveLength > 0) {
|
||||||
|
outColor
|
||||||
|
} else { // 计算渐变的颜色
|
||||||
|
val rate = (itemSize - Math.abs(moveLength)) / itemSize
|
||||||
|
computeGradientColor(centerColor, outColor, rate)
|
||||||
|
}
|
||||||
|
} else if (relative == 0) { // 中间item
|
||||||
|
val rate = Math.abs(moveLength) / itemSize
|
||||||
|
color = computeGradientColor(centerColor, outColor, rate)
|
||||||
|
}
|
||||||
|
mPaint.color = color
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** default out text size 18dp */
|
||||||
|
var sOutTextSize = 18 // dp
|
||||||
|
|
||||||
|
/** default center text size 20dp */
|
||||||
|
var sCenterTextSize = 20 // dp
|
||||||
|
|
||||||
|
/** default center text color */
|
||||||
|
var sCenterColor = "#41bc6a".toColorInt()
|
||||||
|
|
||||||
|
/** default out text color */
|
||||||
|
var sOutColor = "#666666".toColorInt()
|
||||||
|
|
||||||
|
/** Top and bottom shadows colors */
|
||||||
|
var sShadowColors =
|
||||||
|
intArrayOf(Color.WHITE, "#88ffffff".toColorInt(), "#00FFFFFF".toColorInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
55
pickerview/src/main/res/layout/dialog_pickerview_default.xml
Normal file
55
pickerview/src/main/res/layout/dialog_pickerview_default.xml
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@android:color/white">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn_cancel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:paddingRight="20dp"
|
||||||
|
android:text="取消"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="16dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="#253238"
|
||||||
|
android:textSize="18dp"
|
||||||
|
tools:text="title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn_confirm"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="right|center_vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:paddingRight="20dp"
|
||||||
|
android:text="确定"
|
||||||
|
android:textColor="#41bc6a"
|
||||||
|
android:textSize="16dp" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0.5dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:background="#e5e5e5" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
39
pickerview/src/main/res/values/attrs.xml
Normal file
39
pickerview/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- 添加线模式 -->
|
||||||
|
<declare-styleable name="BasePickerView">
|
||||||
|
<attr name="pv_visible_item_count" format="integer" />
|
||||||
|
<!-- item尺寸 -->
|
||||||
|
<attr name="pv_item_size" format="dimension" />
|
||||||
|
<!-- 中间item的位置,默认为0-->
|
||||||
|
<attr name="pv_center_item_position" format="integer" />
|
||||||
|
<!-- 是否循环滚动,默认为true,开启-->
|
||||||
|
<attr name="pv_is_circulation" format="boolean" />
|
||||||
|
<!-- 不允许父组件拦截触摸事件,设置为true为不允许拦截,此时该设置才生效 -->
|
||||||
|
<attr name="pv_disallow_intercept_touch" format="boolean" />
|
||||||
|
<!-- 滚动的方向-->
|
||||||
|
<attr name="pv_orientation" format="string">
|
||||||
|
<enum name="horizontal" value="1" />
|
||||||
|
<enum name="vertical" value="2" />
|
||||||
|
</attr>
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
|
<declare-styleable name="PickerView">
|
||||||
|
<!-- out min text size -->
|
||||||
|
<attr name="pv_out_text_size" format="dimension" />
|
||||||
|
<!-- center max text size -->
|
||||||
|
<attr name="pv_center_text_size" format="dimension" />
|
||||||
|
<!-- out text color -->
|
||||||
|
<attr name="pv_start_color" format="color" />
|
||||||
|
<!-- center text color -->
|
||||||
|
<attr name="pv_end_color" format="color" />
|
||||||
|
<!-- alignment -->
|
||||||
|
<attr name="pv_alignment" format="enum">
|
||||||
|
<enum name="center" value="1" />
|
||||||
|
<enum name="left" value="2" />
|
||||||
|
<enum name="right" value="3" />
|
||||||
|
</attr>
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
|
</resources>
|
||||||
3
pickerview/src/main/res/values/strings.xml
Normal file
3
pickerview/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">PickerView</string>
|
||||||
|
</resources>
|
||||||
7
pickerview/src/main/res/values/styles.xml
Normal file
7
pickerview/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="BottomSheetDialog" parent="Theme.Design.Light.BottomSheetDialog">
|
||||||
|
<item name="android:colorBackground">@android:color/transparent</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@ -16,9 +16,10 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/public/") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "FileRecovery"
|
rootProject.name = "FileRecovery"
|
||||||
include(":app")
|
include(":app",":pickerview")
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user