自定义时间范围筛选
This commit is contained in:
parent
d610ef5d53
commit
592918d6ce
@ -54,4 +54,5 @@ dependencies {
|
||||
kapt (libs.compiler)
|
||||
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||
implementation ("com.google.android.material:material:1.13.0")
|
||||
implementation(project(":pickerview"))
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package com.ux.video.file.filerecovery
|
||||
|
||||
import android.app.Application
|
||||
import org.jaaksi.pickerview.widget.BasePickerView
|
||||
|
||||
class App: Application() {
|
||||
|
||||
@ -10,5 +11,8 @@ class App: Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
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.databinding.ActivityMainBinding
|
||||
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
|
||||
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
|
||||
private var dialogCustomerDateStart:DatePickerDialogFragment? = null
|
||||
private var dialogPermission: PermissionDialogFragment? = null
|
||||
//是否正确引导用户打开所有文件管理权限
|
||||
private var isRequestPermission = false
|
||||
@ -94,9 +96,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
intentCheck()
|
||||
}
|
||||
}
|
||||
binding.btnPermission.setOnClickListener {
|
||||
|
||||
binding.tvTitle.setOnClickListener {
|
||||
|
||||
showDatePicker()
|
||||
|
||||
}
|
||||
binding.btnScanAllPhoto.setOnClickListener {
|
||||
@ -242,4 +244,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
super.onDestroy()
|
||||
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
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.ux.video.file.filerecovery.R
|
||||
import com.ux.video.file.filerecovery.databinding.CommonLayoutSortItemBinding
|
||||
import com.ux.video.file.filerecovery.databinding.DialogSortBinding
|
||||
import com.google.android.material.datepicker.MaterialDatePicker
|
||||
import com.google.android.material.datepicker.CalendarConstraints
|
||||
import com.google.android.material.datepicker.DateValidatorPointForward
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import com.ux.video.file.filerecovery.databinding.DialogFilterCustomerDateBinding
|
||||
import com.ux.video.file.filerecovery.utils.Common
|
||||
import com.ux.video.file.filerecovery.utils.CustomTextView
|
||||
import org.jaaksi.pickerview.picker.TimePicker
|
||||
import org.jaaksi.pickerview.picker.TimePicker.Companion.TYPE_DATE
|
||||
import org.jaaksi.pickerview.picker.TimePicker.OnTimeSelectListener
|
||||
import org.jaaksi.pickerview.widget.DefaultCenterDecoration
|
||||
import java.util.Date
|
||||
|
||||
class DatePickerDialogFragment(val onClickSort: (type: Int) -> Unit) : DialogFragment() {
|
||||
|
||||
private lateinit var binding: DialogSortBinding
|
||||
class DatePickerDialogFragment(
|
||||
var mContext: Context,
|
||||
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() {
|
||||
super.onStart()
|
||||
dialog?.window?.apply {
|
||||
@ -28,7 +43,7 @@ class DatePickerDialogFragment(val onClickSort: (type: Int) -> Unit) : DialogFra
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
setGravity(Gravity.BOTTOM)
|
||||
setGravity(Gravity.CENTER)
|
||||
setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
|
||||
}
|
||||
}
|
||||
@ -37,10 +52,114 @@ class DatePickerDialogFragment(val onClickSort: (type: Int) -> Unit) : DialogFra
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): 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
|
||||
}
|
||||
|
||||
|
||||
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 com.ux.video.file.filerecovery.databinding.CommonLayoutFilterItemBinding
|
||||
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.CustomTextView
|
||||
|
||||
class FilterPopupWindows(
|
||||
context: Context, list: Array<String>,
|
||||
selectedPos: Int,
|
||||
var selectedPos: Int,
|
||||
onClickConfirm: (value: String) -> Unit,
|
||||
var onClickDismiss: () -> Unit
|
||||
) :
|
||||
@ -46,6 +47,12 @@ class FilterPopupWindows(
|
||||
}
|
||||
|
||||
setData(list, selectedPos, onClickConfirm)
|
||||
Common.showLog("--------------FilterPopupWindows init")
|
||||
}
|
||||
|
||||
fun updateSelectPos(index: Int) {
|
||||
selectedPos = index
|
||||
setPosSelect(selectedPos)
|
||||
}
|
||||
|
||||
private fun setData(
|
||||
@ -83,21 +90,18 @@ class FilterPopupWindows(
|
||||
val child = parentLayout.getChildAt(i)
|
||||
if (child is RelativeLayout) {
|
||||
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) {
|
||||
val other = child.getChildAt(j)
|
||||
if (other is CustomTextView) {
|
||||
other.text.toString().let {
|
||||
setPosSelect(i)
|
||||
dismiss()
|
||||
}
|
||||
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) {
|
||||
showAsDropDown(anchor, xOff, yOff)
|
||||
|
||||
@ -1,18 +1,13 @@
|
||||
package com.ux.video.file.filerecovery.photo
|
||||
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import androidx.activity.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
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.base.BaseActivity
|
||||
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.filterBySize
|
||||
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.filterWithinMonthsList
|
||||
import com.ux.video.file.filerecovery.utils.ExtendFunctions.filterWithinDateRange
|
||||
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.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.ScanManager
|
||||
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.ScanRepository
|
||||
import kotlinx.coroutines.launch
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
|
||||
@ -47,6 +40,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
val FILTER_DATE_1 = 1
|
||||
val FILTER_DATE_6 = 6
|
||||
val FILTER_DATE_24 = 24
|
||||
val FILTER_DATE_CUSTOMER = 0
|
||||
|
||||
val FILTER_SIZE_ALL = -1
|
||||
val FILTER_SIZE_1 = 1
|
||||
@ -70,6 +64,9 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
|
||||
private var dialogDeleting: DeletingDialogFragment? = null
|
||||
|
||||
private var dialogCustomerDateStart: DatePickerDialogFragment? = null
|
||||
private var dialogCustomerDateEnd: DatePickerDialogFragment? = null
|
||||
|
||||
|
||||
//默认倒序排序
|
||||
private var sortReverse = true
|
||||
@ -80,7 +77,10 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
//筛选大小,默认全部-1
|
||||
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 filterLayoutPopupWindows: FilterPopupWindows? = null
|
||||
|
||||
@ -282,9 +282,7 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
val bPx = 6.dpToPx(context)
|
||||
setPadding(aPx, 0, bPx, 0)
|
||||
clipToPadding = false
|
||||
|
||||
layoutManager = GridLayoutManager(context, columns)
|
||||
// resetItemDecorationOnce(mItemDecoration)
|
||||
adapter = sizeSortAdapter
|
||||
|
||||
}
|
||||
@ -299,20 +297,52 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
filterDateLayout.setOnClickListener {
|
||||
setItemSelect(it as LinearLayout, true)
|
||||
resources.getStringArray(R.array.filter_date).let { data ->
|
||||
filterDatePopupWindows = filterDatePopupWindows ?: FilterPopupWindows(
|
||||
filterDatePopupWindows = filterDatePopupWindows ?: DateFilterPopupWindows(
|
||||
this@PhotoSortingActivity,
|
||||
data,
|
||||
0,
|
||||
{ clickValue ->
|
||||
{ clickValue, showDialog ->
|
||||
when (clickValue) {
|
||||
data[0] -> filterDate = FILTER_DATE_ALL
|
||||
data[1] -> filterDate = FILTER_DATE_1
|
||||
data[2] -> filterDate = FILTER_DATE_6
|
||||
data[3] -> filterDate = FILTER_DATE_24
|
||||
data[4] -> showDatePicker()
|
||||
}
|
||||
data[0] -> {
|
||||
filterDate = FILTER_DATE_ALL
|
||||
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)
|
||||
}
|
||||
|
||||
@ -408,13 +438,18 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
* 执行筛选结果
|
||||
*/
|
||||
private fun startFilter() {
|
||||
Common.showLog("--------------开始筛选")
|
||||
when (binding.recyclerView.adapter) {
|
||||
//当前是时间排序
|
||||
is PhotoDisplayDateAdapter -> {
|
||||
//确定当前排序
|
||||
val list = if (sortReverse) sortByDateReverse else sortedByDatePositive
|
||||
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)
|
||||
.let { currentList ->
|
||||
//对筛选后的数据与实际选中集合对比 ,得出筛选后显示的数据中的选中数据
|
||||
@ -434,7 +469,11 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
is PhotoDisplayDateChildAdapter -> {
|
||||
val list = if (sortReverse) sortBySizeBigToSmall else sortBySizeSmallToBig
|
||||
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)
|
||||
.let { currentList ->
|
||||
//对筛选后的数据与实际选中集合对比 ,得出筛选后显示的数据中的选中数据
|
||||
@ -461,33 +500,75 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
}
|
||||
|
||||
|
||||
fun showDatePicker() {
|
||||
// 创建日期选择器构建器
|
||||
val builder = MaterialDatePicker.Builder.datePicker()
|
||||
builder.setTitleText("选择日期")
|
||||
private fun showStartDateDialog(isNeedSetSelected: Boolean,currentDate: Date?) {
|
||||
|
||||
// 可选:限制可选日期,比如只能选择今天之后的日期
|
||||
val constraintsBuilder = CalendarConstraints.Builder()
|
||||
constraintsBuilder.setValidator(DateValidatorPointForward.now()) // 今天之后
|
||||
builder.setCalendarConstraints(constraintsBuilder.build())
|
||||
dialogCustomerDateStart = dialogCustomerDateStart ?: DatePickerDialogFragment(
|
||||
this,
|
||||
getString(R.string.start_date),
|
||||
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))
|
||||
println("用户选择的日期:$dateString")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun showEndDateDialog(isNeedSetSelected: Boolean,currentDate: Date?) {
|
||||
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() {
|
||||
dialogRecovering = dialogRecovering ?: RecoveringDialogFragment(filterSelectedSetList.size){
|
||||
dialogRecovering =
|
||||
dialogRecovering ?: RecoveringDialogFragment(filterSelectedSetList.size) {
|
||||
complete()
|
||||
}
|
||||
dialogRecovering?.show(supportFragmentManager, "")
|
||||
@ -500,6 +581,9 @@ class PhotoSortingActivity : BaseActivity<ActivityPhotoSortingBinding>() {
|
||||
dialogDeleting?.show(supportFragmentManager, "")
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除或者恢复完成
|
||||
*/
|
||||
private fun complete() {
|
||||
dialogDeleting?.dismiss()
|
||||
dialogRecovering?.dismiss()
|
||||
|
||||
@ -5,7 +5,9 @@ import android.icu.text.SimpleDateFormat
|
||||
import android.icu.util.Calendar
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.ux.video.file.filerecovery.App
|
||||
import com.ux.video.file.filerecovery.R
|
||||
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
|
||||
@ -32,6 +34,7 @@ object Common {
|
||||
|
||||
val rootDir = Environment.getExternalStorageDirectory()
|
||||
val dateFormat = SimpleDateFormat("MMMM d,yyyy", Locale.ENGLISH)
|
||||
val chineseFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINESE)
|
||||
val recoveryPhotoDir = "MyAllRecovery/Photo"
|
||||
|
||||
/**
|
||||
@ -127,14 +130,21 @@ object Common {
|
||||
/**
|
||||
* 设置全部子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) {
|
||||
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 totalSelectedCount = list.count {
|
||||
val isSelected = it.path in selected
|
||||
@ -176,6 +189,19 @@ object Common {
|
||||
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) {
|
||||
Log.d("============", msg)
|
||||
}
|
||||
|
||||
@ -12,8 +12,8 @@ class CustomTextView @JvmOverloads constructor(
|
||||
) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyleAttr) {
|
||||
|
||||
companion object {
|
||||
private var regular: Typeface? = null
|
||||
private var bold: Typeface? = null
|
||||
private var regular: Typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
|
||||
var bold: Typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
|
||||
private var alimama: Typeface? = null
|
||||
}
|
||||
|
||||
@ -27,25 +27,20 @@ class CustomTextView @JvmOverloads constructor(
|
||||
selectedText = typedArray.getString(R.styleable.CustomTextView_selected_text)
|
||||
normalText = typedArray.getString(R.styleable.CustomTextView_normal_text)
|
||||
typedArray.recycle()
|
||||
|
||||
Typeface.create("sans-serif-light", Typeface.NORMAL) // Roboto Light
|
||||
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
|
||||
if (alimama == null) {
|
||||
alimama = Typeface.createFromAsset(context.assets, "fonts/alimama.ttf")
|
||||
}
|
||||
when (type) {
|
||||
0 -> {
|
||||
typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
|
||||
typeface = regular
|
||||
}
|
||||
|
||||
1 -> {
|
||||
|
||||
typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
|
||||
typeface = bold
|
||||
}
|
||||
|
||||
2 -> {
|
||||
if (alimama == null) {
|
||||
alimama = Typeface.createFromAsset(context.assets, "fonts/alimama.ttf")
|
||||
}
|
||||
typeface = alimama
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import android.os.Parcelable
|
||||
import android.util.TypedValue
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.ux.video.file.filerecovery.photo.ResultPhotosFiles
|
||||
import java.util.Date
|
||||
|
||||
object ExtendFunctions {
|
||||
|
||||
@ -40,17 +41,57 @@ object ExtendFunctions {
|
||||
/**
|
||||
* 按时间筛选:最近 N 个月
|
||||
*/
|
||||
fun List<ResultPhotosFiles>.filterWithinMonthsList(months: Int): List<ResultPhotosFiles> {
|
||||
if (months == -1) return this
|
||||
// fun List<ResultPhotosFiles>.filterWithinMonthsList(months: Int): List<ResultPhotosFiles> {
|
||||
// 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()
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 按文件大小筛选:区间 [minSize, maxSize]
|
||||
@ -66,19 +107,61 @@ object ExtendFunctions {
|
||||
/**
|
||||
* 按时间筛选:最近 N 个月
|
||||
*/
|
||||
fun List<Pair<String, List<ResultPhotosFiles>>>.filterWithinMonths(months: Int): List<Pair<String, List<ResultPhotosFiles>>> {
|
||||
if (months == -1) return this
|
||||
// fun List<Pair<String, List<ResultPhotosFiles>>>.filterWithinMonths(months: Int): List<Pair<String, List<ResultPhotosFiles>>> {
|
||||
// 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 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)
|
||||
|
||||
return when {
|
||||
// -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()
|
||||
_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_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
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_delete">#fdad00</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>
|
||||
@ -73,6 +73,8 @@ wait..</string>
|
||||
<string name="recovered_success">Recovered successfully!</string>
|
||||
<string name="deleted_success">Deleted successfully!</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">
|
||||
<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 {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url = uri("https://maven.aliyun.com/repository/public/") }
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "FileRecovery"
|
||||
include(":app")
|
||||
include(":app",":pickerview")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user