先上一个Telegram的媒体样式, 后面是自定义是实现效果
Telegram.png
在来看一下实现的效果
仿TG.png 仿TG1.png
大概说一下实现的方式,目前用的是DialogFragment全屏实现,自定义滚动视图,利用pictureselector图片库 获取数据源,列表第一个是打开的相机,根据官方提供的相机库 实现绑定生命周期显示相机。
先看滚动视图,首先你得知道你的滚动视图分为几个view,占几部分,我这里写的固定view,如果满足不了你的需求请自行修改
DialogFragment中滚动View
class ChatPhotoAlertNestedScrollingParent @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr), NestedScrollingParent3 {
// 标题栏
private var mFlTitle: View? = null
// 滚动的间距
private var mViewSpace: View? = null
// 滚动底部的view
private var mScrollBottomView: View? = null
// 滚动的背景布局头
private var mClBottomLayoutTitle: ConstraintLayout? = null
private var mTabBgStartColor = Color.WHITE
private var mTabBgEndColor = Color.WHITE
private var mTabBgColor = 0
// 背景 圆角
private var mTabBg: GradientDrawable = GradientDrawable()
private var mTabBgRadius: FloatArray = FloatArray(8)
private var mMaxTabBgRadius = 0f
private val mHelper: NestedScrollingParentHelper = NestedScrollingParentHelper(this)
private var mSnapAnimator: ValueAnimator? = null
private var mLastStartedType = 0
private var mScrollRangeTop = 0
private var mScrollRangeBottom = 0
private var mCurrentScrollRange = 0
private var mTitleChange = false
private var isScrollTopShowTitle = false
private var mArgbEvaluator: ArgbEvaluator = ArgbEvaluator()
private var isDoPerformAnyCallbacks = true
private var mOnOffsetScrollRangeListener: OnOffsetScrollRangeListener? = null
init {
mArgbEvaluator = ArgbEvaluator()
mTabBgColor = mTabBgStartColor
mMaxTabBgRadius = getDefaultTabBgRadius().toFloat()
mTabBg.setColor(mTabBgStartColor)
mTabBgRadius = FloatArray(8)
mTabBgRadius[0] = mMaxTabBgRadius.also { mTabBgRadius[3] = it }
.also { mTabBgRadius[2] = it }.also { mTabBgRadius[1] = it }
mTabBgRadius[4] = 0.also { mTabBgRadius[7] = it.toFloat() }.also {
mTabBgRadius[6] =
it.toFloat()
}.also { mTabBgRadius[5] = it.toFloat() }.toFloat()
mTabBg.cornerRadii = mTabBgRadius
}
override fun onFinishInflate() {
super.onFinishInflate()
mFlTitle = findViewById(R.id.fl_title)
mViewSpace = findViewById(R.id.view_space)
mScrollBottomView = findViewById(R.id.ll_scroll_bottom_layout)
mClBottomLayoutTitle = findViewById(R.id.cl_bottom_top_title)
mClBottomLayoutTitle?.background = mTabBg
}
private fun getDefaultTabBgRadius(): Int {
return getDimen(R.dimen.dp_16)
}
private fun getDimen(@DimenRes dimenId: Int): Int {
return resources.getDimensionPixelSize(dimenId)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val mViewSpaceHeight = mViewSpace!!.measuredHeight
val mFlTitleHeight = mFlTitle!!.measuredHeight
val mViewSpaceMarginTop = (mViewSpace!!.layoutParams as MarginLayoutParams).topMargin
// 获取向上滑动的距离
mScrollRangeTop = mViewSpaceHeight + mViewSpaceMarginTop + mFlTitleHeight
val update = mCurrentScrollRange != 0 && mScrollRangeBottom == mCurrentScrollRange
// 屏幕总高度 - 上方空间高度 - 状态栏高度
val mViewSpaceBottom = ScreenUtils.getScreenHeight() - mViewSpaceHeight
// 获取向下滑动的距离 布局距离下方的3分之1
mScrollRangeBottom = -(mViewSpaceBottom - mViewSpaceBottom / 3)
if (update) {
mCurrentScrollRange = mScrollRangeBottom
}
val scrollWidthSpec = MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY)
val scrollHeightSpec =
MeasureSpec.makeMeasureSpec(measuredHeight - mFlTitleHeight, MeasureSpec.EXACTLY)
// val scrollHeightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY);
mScrollBottomView!!.measure(scrollWidthSpec, scrollHeightSpec)
if (mOnOffsetScrollRangeListener != null) {
mOnOffsetScrollRangeListener!!.offsetScroll(0f,
mCurrentScrollRange,
mScrollRangeTop,
mScrollRangeBottom,
isScrollTopShowTitle)
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
val mViewSpaceHeight =
ScreenUtils.getScreenHeight() - mScrollBottomView!!.measuredHeight + mViewSpace!!.measuredHeight
mViewSpace!!.layout(left, mFlTitle!!.measuredHeight, right, mViewSpaceHeight)
val t = mScrollRangeTop - mCurrentScrollRange + mFlTitle!!.measuredHeight
mScrollBottomView!!.layout(left, t, right, t + mScrollBottomView!!.measuredHeight)
}
override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
val started = axes and ViewCompat.SCROLL_AXIS_VERTICAL != 0
if (started) {
if (mSnapAnimator != null) {
mSnapAnimator!!.cancel()
}
}
mLastStartedType = type
return started
}
override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) {
mHelper.onNestedScrollAccepted(child, target, axes, type)
}
override fun onStopNestedScroll(target: View, type: Int) {
if (mLastStartedType == ViewCompat.TYPE_TOUCH || type == ViewCompat.TYPE_NON_TOUCH) {
if (mSnapAnimator == null || !mSnapAnimator!!.isRunning) {
snap()
}
}
}
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int,
consumed: IntArray,
) {
onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type)
if (dyUnconsumed < 0) {
val bottom: Int
if (type == ViewCompat.TYPE_TOUCH) {
bottom = mScrollRangeBottom
} else if (mCurrentScrollRange < 0) {
bottom = mScrollRangeBottom
if (dyUnconsumed > -10 && mCurrentScrollRange < mScrollRangeBottom * 0.25f) {
ViewCompat.stopNestedScroll(target, type)
}
} else {
bottom = 0
}
consumed[1] = offsetScrollView(dyUnconsumed, mScrollRangeTop, bottom)
}
}
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int,
) {
}
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
if (dy > 0) {
if (type == ViewCompat.TYPE_TOUCH || mCurrentScrollRange > 0) {
consumed[1] = offsetScrollView(dy, mScrollRangeTop, mScrollRangeBottom)
} else {
offsetScrollView(dy, 0, mScrollRangeBottom)
consumed[1] = dy
}
}
}
private fun offsetScrollView(dy: Int) {
offsetScrollView(dy, mScrollRangeTop, mScrollRangeBottom, true)
}
private fun offsetScrollView(dy: Int, top: Int, bottom: Int): Int {
return offsetScrollView(dy, top, bottom, false)
}
private fun offsetScrollView(dy: Int, top: Int, bottom: Int, anim: Boolean): Int {
var tempDy = dy
if (tempDy >= 0 && mCurrentScrollRange >= top) {
return 0
}
if (tempDy <= 0 && mCurrentScrollRange <= bottom) {
return 0
}
val result = tempDy
if (mCurrentScrollRange <= 0 && !anim) {
tempDy /= 1.5f.toInt()
}
mCurrentScrollRange += tempDy
if (mCurrentScrollRange > top) {
tempDy -= mCurrentScrollRange - top
mCurrentScrollRange = top
} else if (mCurrentScrollRange < bottom) {
tempDy -= mCurrentScrollRange - bottom
mCurrentScrollRange = bottom
}
changeTitle(mCurrentScrollRange.toFloat())
ViewCompat.offsetTopAndBottom(mScrollBottomView!!, -tempDy)
return result
}
private fun changeTitle(current: Float) {
val fraction: Float = if (current < 0) {
0f
} else if (current >= mScrollRangeTop) {
1f
} else {
current / mScrollRangeTop
}
// int titleColor = (int) mArgbEvaluator.evaluate(fraction, mTitleSearchBgStartColor, mTitleSearchBgEndColor);
// mFlTitle.getBackground().mutate().setAlpha((int) (fraction * 0xFF));
val radius = mMaxTabBgRadius * (1 - fraction)
mTabBgRadius[3] = radius
mTabBgRadius[2] = mTabBgRadius[3]
mTabBgRadius[1] = mTabBgRadius[2]
mTabBgRadius[0] = mTabBgRadius[1]
mTabBg.cornerRadii = mTabBgRadius
val tabColor = mArgbEvaluator.evaluate(fraction, mTabBgColor, mTabBgEndColor) as Int
mTabBg.setColor(tabColor)
mClBottomLayoutTitle!!.background = mTabBg
dispatchTitleChange(fraction)
if (mOnOffsetScrollRangeListener != null && isDoPerformAnyCallbacks) {
mOnOffsetScrollRangeListener!!.offsetScroll(fraction,
mCurrentScrollRange,
mScrollRangeTop,
mScrollRangeBottom,
isScrollTopShowTitle)
mOnOffsetScrollRangeListener!!.isScrollTopShowTitle(mCurrentScrollRange,
mScrollRangeTop,
mCurrentScrollRange == mScrollRangeTop)
}
}
private fun dispatchTitleChange(fraction: Float) {
val change = fraction > 0
if (change != mTitleChange) {
mTitleChange = change
}
}
fun setExpand(expand: Boolean) {
if (expand) {
anim(mCurrentScrollRange, mScrollRangeBottom)
} else {
anim(mCurrentScrollRange, 0)
}
}
private fun snap() {
val start = mCurrentScrollRange
if (start != mScrollRangeTop) {
val end: Int = if (start < mScrollRangeBottom * 0.5f) {
// end = mScrollRangeBottom;
0
} else if (start > mScrollRangeTop * 0.5f) {
mScrollRangeTop
} else {
0
}
anim(start, end)
}
}
private fun anim(start: Int, end: Int) {
if (start == end) return
isScrollTopShowTitle = end == mScrollRangeTop
if (mSnapAnimator == null) {
mSnapAnimator = ValueAnimator.ofInt(start, end)
mSnapAnimator?.addUpdateListener { animation: ValueAnimator ->
val value = animation.animatedValue as Int
offsetScrollView(value - mCurrentScrollRange)
}
} else {
mSnapAnimator!!.cancel()
mSnapAnimator!!.setIntValues(start, end)
}
mSnapAnimator!!.duration = 250
mSnapAnimator!!.start()
}
fun setDoPerformAnyCallbacks(doPerformAnyCallbacks: Boolean) {
isDoPerformAnyCallbacks = doPerformAnyCallbacks
}
fun setOnOffsetScrollRangeListener(listener: OnOffsetScrollRangeListener) {
mOnOffsetScrollRangeListener = listener
}
// 滚动的距离
interface OnOffsetScrollRangeListener {
fun offsetScroll(
fraction: Float,
offset: Int,
scrollRangeTop: Int,
scrollRangeBottom: Int,
isScrollTopShowTitle: Boolean,
)
fun isScrollTopShowTitle(offset: Int, scrollRangeTop: Int, isScrollTopShowTitle: Boolean)
}
}
这里面主要就是测量view的大小,计算出滚动的距离,还有显示的位置等;(滚动距离还有显示位置根据自己需求自行修改)
下面才是媒体显示的页面,主要介绍一下逻辑部分(布局就不贴了,尾部给链接,自行查看)
在滚动的时候 上面的导航标题栏显示隐藏;并且可以点击切换相册媒体文件,点击图片带有选中的序号,按照顺序显示;下方带有说明的输入框;在列表的第一个是打开的相机,这里相机是需要绑定生命周期的,直接传入DialogFragment,点击之后进入自定义拍照相机页面, 拍照介绍返回图片添加进入列表显示
DialogFragment
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
initView()
clickListener()
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.TransparentDialog)
onCreateConfigLoader()
}
override fun onAttach(context: Context) {
super.onAttach(context)
mActivity = context as FragmentActivity
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
binding = DialogChatAttachPhotoAlertBinding.inflate(layoutInflater, null, false)
if (Build.VERSION.SDK_INT >= 30) {
dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
} else {
dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
}
dialog.window?.statusBarColor = Color.TRANSPARENT
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
val params = dialog.window!!.attributes
params.gravity = Gravity.BOTTOM
params.height = ViewGroup.LayoutParams.MATCH_PARENT
if (Build.VERSION.SDK_INT >= 28) {
params.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
dialog.window?.attributes = params
dialog.setContentView(binding.root)
dialog.setOnKeyListener { _, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
dismissWithDelayRun()
true
} else {
false
}
}
return dialog
}
// 初始化获取数据源的状态
private fun onCreateConfigLoader() {
PictureSelectionConfig.imageEngine = GlideEngine.createGlideEngine()
config = PictureSelectionConfig.getInstance()
config.chooseMode = SelectMimeType.ofAll()
config.isDisplayCamera = true
config.isPageSyncAsCount = true
config.isPageStrategy = true
config.isGif = true
config.isBmp = true
config.maxSelectNum = Int.MAX_VALUE
mLoader = if (config.isPageStrategy) LocalMediaPageLoader() else LocalMediaLoader()
mLoader.initConfig(mActivity, config)
}
@SuppressLint("ClickableViewAccessibility", "NotifyDataSetChanged")
private fun initView() {
// 弹出动画之后 设置背景半透明
mHandler.postDelayed({
// 在显示之后 设置没有动画 否则跳转页面在返回是会有弹出动画
dialog?.window?.setWindowAnimations(R.style.DialogNoAnimation)
binding.nestedScrolling.setDoPerformAnyCallbacks(true)
val animator = ValueAnimator.ofInt(0, 128)
animator.duration = 200
animator.addUpdateListener {
val animatedValue = it.animatedValue as Int
dialog?.window!!.statusBarColor =
ColorUtils.setAlphaComponent(Color.parseColor("#80222229"), animatedValue)
binding.nestedScrolling.setBackgroundColor(
ColorUtils.setAlphaComponent(Color.parseColor("#80222229"), animatedValue)
)
}
animator.start()
}, 400)
binding.nestedScrolling.setOnOffsetScrollRangeListener(object :
ChatPhotoAlertNestedScrollingParent.OnOffsetScrollRangeListener {
override fun offsetScroll(
fraction: Float,
offset: Int,
scrollRangeTop: Int,
scrollRangeBottom: Int,
isScrollTopShowTitle: Boolean,
) {
this@ChatAttachPhotoAlert.onScrollOffset = offset
this@ChatAttachPhotoAlert.scrollTop = scrollRangeTop
this@ChatAttachPhotoAlert.scrollBottom = scrollRangeBottom
}
override fun isScrollTopShowTitle(
offset: Int,
scrollRangeTop: Int,
isScrollTopShowTitle: Boolean,
) {
if (offset == scrollTop) {
dialog?.window!!.statusBarColor = Color.parseColor("#0096f0")
binding.flTitle.visible()
} else {
dialog?.window!!.statusBarColor = Color.parseColor("#80222229")
binding.flTitle.invisible()
}
}
})
initAlbumListPopWindow()
// binding.recyclerViewPreview.setOnTouchListener { _, event ->
// setOnTouchListenerScrollHandler(event)
// return@setOnTouchListener false
// }
binding.recyclerView.setOnTouchListener { _, event ->
setOnTouchListenerScrollHandler(event)
return@setOnTouchListener false
}
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.setReachBottomRow(RecyclerPreloadView.BOTTOM_PRELOAD)
binding.recyclerView.setOnRecyclerViewPreloadListener(this)
binding.recyclerView.layoutManager = mLayoutManager
binding.recyclerView.addItemDecoration(GridSpacingItemDecoration(3, 6.dp2px(), true))
mAdapter = BasePictureAdapter(this, openCameraClick = {
openCameraPermissions()
}, onItemClickListener = { _, position ->
onStartPreview(position)
}, onItemLongClick = { _, position ->
if (mDragSelectTouchListener != null) {
val vibrator = activity?.getSystemService(Service.VIBRATOR_SERVICE) as Vibrator
vibrator.vibrate(50)
mDragSelectTouchListener?.startSlideSelection(if (mAdapter.isDisplayCamera()) position - 1 else position)
}
}, onSelectListener = { selectedView, ivPicture, data, position ->
val selectResultCode = confirmSelect(data, selectedView.isSelected)
selectedMedia(selectedView, ivPicture, isSelected(data))
mAdapter.notifyItemChanged(position)
if (selectResultCode == SelectedManager.ADD_SUCCESS) {
val animation = AnimationUtils.loadAnimation(context, com.luck.picture.lib.R.anim.ps_anim_modal_in)
selectedView.startAnimation(animation)
}
})
mAdapter.setDisplayCamera(true)
binding.recyclerView.adapter = mAdapter
// 图片预览
// binding.recyclerViewPreview.setHasFixedSize(true)
// binding.recyclerViewPreview.layoutManager = LinearLayoutManager(mActivity)
// binding.recyclerViewPreview.adapter =
// mAdapterPreview.apply { addItemBinder(PreviewImageGroupAdapter()) }
SelectedManager.addAllSelectResult(mAdapter.data)
binding.recyclerView.setOnRecyclerViewScrollStateListener(object :
OnRecyclerViewScrollStateListener {
override fun onScrollFast() {
if (PictureSelectionConfig.imageEngine != null) {
PictureSelectionConfig.imageEngine.pauseRequests(context)
}
}
override fun onScrollSlow() {
if (PictureSelectionConfig.imageEngine != null) {
PictureSelectionConfig.imageEngine.resumeRequests(context)
}
}
})
val selectedPosition = HashSet<Int>()
val slideSelectionHandler =
SlideSelectionHandler(object : SlideSelectionHandler.ISelectionHandler {
override fun getSelection(): HashSet<Int> {
for (i in 0 until SelectedManager.getSelectCount()) {
val media = SelectedManager.getSelectedResult()[i]
selectedPosition.add(media.position)
}
return selectedPosition
}
override fun changeSelection(
start: Int,
end: Int,
isSelected: Boolean,
calledFromOnStart: Boolean,
) {
// 下标是0的时候不处理 因为是相机
// if (start == 0 || end == 0) return
val adapterData: ArrayList<LocalMedia> = mAdapter.data as ArrayList
if (adapterData.size == 0 || start > adapterData.size) return
val media = adapterData[start]
val selectResultCode: Int =
confirmSelect(media, SelectedManager.getSelectedResult().contains(media))
mDragSelectTouchListener?.setActive(selectResultCode != SelectedManager.INVALID)
}
})
mDragSelectTouchListener = SlideSelectTouchListener()
.setRecyclerViewHeaderCount(if (mAdapter.isDisplayCamera()) 1 else 0)
.withSelectListener(slideSelectionHandler)
mDragSelectTouchListener?.let { binding.recyclerView.addOnItemTouchListener(it) }
val emojiTheming = EmojiTheming(
ContextCompat.getColor(mActivity, R.color.color_F2F3F5),
ContextCompat.getColor(mActivity, R.color.black),
ContextCompat.getColor(mActivity, R.color.blue_color),
ContextCompat.getColor(mActivity, R.color.color_ECEDF1),
ContextCompat.getColor(mActivity, R.color.blue_color),
ContextCompat.getColor(mActivity, R.color.blue_color)
)
emojiPopup = EmojiPopup(rootView = binding.emotionContainerFrameLayout,
editText = binding.layoutBottomEdit.editText,
onEmojiPopupShownListener = {
binding.layoutBottomEdit.emotionImageView.setImageResource(R.drawable.icon_chat_key)
},
onEmojiPopupDismissListener = {
binding.layoutBottomEdit.emotionImageView.setImageResource(R.drawable.icon_chat_emo)
},
onSoftKeyboardCloseListener = {},
onSoftKeyboardOpenListener = {},
onEmojiBackspaceClickListener = {},
onEmojiClickListener = {}, theming = emojiTheming
)
requestLoadData()
}
private fun clickListener() {
binding.layoutBottomEdit.editText.doAfterTextChanged {
//文字或换行导致输入框超过两行更换输入框圆角背景
if (binding.layoutBottomEdit.editText.lineCount > 1) {
if (!is6dpRound) {
binding.layoutBottomEdit.llEditSpeak.setBackgroundResource(R.drawable.shape_white_radius_6)
is6dpRound = true
}
} else {
binding.layoutBottomEdit.llEditSpeak.setBackgroundResource(R.drawable.shape_white_radius_20)
is6dpRound = false
}
}
binding.ivBack.setOnClickListener { dismissWithDelayRun() }
binding.llTitle.setOnClickListener {
albumListPopWindow.showAsDropDown(binding.tvTitle)
}
binding.viewSpace.setOnClickListener { dismissWithDelayRun() }
binding.layoutBottomEdit.llEditSpeak.setOnClickListener { KeyboardUtils.showSoftInput(binding.layoutBottomEdit.editText) }
binding.layoutBottomEdit.clBottomSendEdit.setOnLongClickListener { true }
binding.layoutBottomEdit.emotionImageView.setOnClickListener {
emojiPopup?.toggle()
}
binding.tvSelectNum.setOnClickListener {
// switchAlbumPreviewAnimation()
}
binding.sendButton.setOnClickListener { dispatchTransformResult() }
}
/**
* initAlbumListPopWindow
*/
private fun initAlbumListPopWindow() {
albumListPopWindow = AlbumListPopWindow.buildPopWindow(mActivity)
albumListPopWindow.setOnPopupWindowStatusListener(object :
AlbumListPopWindow.OnPopupWindowStatusListener {
override fun onShowPopupWindow() {
if (!config.isOnlySandboxDir) {
AnimUtils.rotateArrow(binding.ivArrow, true)
}
}
override fun onDismissPopupWindow() {
if (!config.isOnlySandboxDir) {
AnimUtils.rotateArrow(binding.ivArrow, false)
}
}
})
albumListPopWindow.setOnIBridgeAlbumWidget { position, curFolder ->
val isDisplayCamera = config.isDisplayCamera && curFolder.bucketId == PictureConfig.ALL.toLong()
mAdapter.setDisplayCamera(isDisplayCamera)
if (position == 0) {
binding.tvTitle.text = StringUtils.getString(R.string.album)
} else {
binding.tvTitle.text = curFolder.folderName
}
val lastFolder = SelectedManager.getCurrentLocalMediaFolder()
val lastBucketId = lastFolder.bucketId
if (curFolder.bucketId != lastBucketId) {
// 1、记录一下上一次相册数据加载到哪了,到时候切回来的时候要续上
val laseFolderData = ArrayList(mAdapter.data)
lastFolder.data = laseFolderData
lastFolder.currentDataPage = mPage
lastFolder.isHasMore = binding.recyclerView.isEnabledLoadMore
// 2、判断当前相册是否请求过,如果请求过则不从MediaStore去拉取了
if (curFolder.data.size > 0 && !curFolder.isHasMore) {
setAdapterData(curFolder.data)
mPage = curFolder.currentDataPage
binding.recyclerView.isEnabledLoadMore = curFolder.isHasMore
binding.recyclerView.smoothScrollToPosition(0)
} else {
// 3、从MediaStore拉取数据
mPage = 1
mLoader.loadPageMediaData(curFolder.bucketId, mPage, config.pageSize,
object : OnQueryDataResultListener<LocalMedia>() {
override fun onComplete(
result: ArrayList<LocalMedia>,
isHasMore: Boolean,
) {
handleSwitchAlbum(result, isHasMore)
}
})
}
}
SelectedManager.setCurrentLocalMediaFolder(curFolder)
albumListPopWindow.dismiss()
if (mDragSelectTouchListener != null && config.isFastSlidingSelect) {
mDragSelectTouchListener!!.setRecyclerViewHeaderCount(if (mAdapter.isDisplayCamera()) 1 else 0)
}
}
}
创建一个透明的dialog,在完全显示之后重新设置一下主题,初始化获取数据源的配置,设置列表适配器,RecyclerView也是用的pictureselector库中的RV,满足长按滑动选择功能;初始化点击事件及标题栏媒体的弹框
// 加载数据 判断权限
private fun requestLoadData() {
if (XXPermissions.isGranted(mActivity, Permission.Group.STORAGE)) {
loadAllAlbumData()
} else {
XXPermissions.with(mActivity)
// 申请多个权限
.permission(Permission.Group.STORAGE)
.request(object : OnPermissionCallback {
override fun onGranted(permissions: MutableList<String>, allGranted: Boolean) {
if (!allGranted) {
val tips = StringUtils.getString(R.string.photos_media_missing_storage_permissions)
permissionsDialog(mActivity, permissions, tips)
return
}
loadAllAlbumData()
}
override fun onDenied(
permissions: MutableList<String>,
doNotAskAgain: Boolean,
) {
if (doNotAskAgain) {
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(mActivity, permissions)
} else {
val tips =
StringUtils.getString(R.string.photos_media_missing_storage_permissions)
permissionsDialog(mActivity, permissions, tips)
}
}
})
}
}
override fun loadAllAlbumData() {
preloadPageFirstData()
mLoader.loadAllAlbum { localMediaFolder ->
handleAllAlbumData(localMediaFolder)
}
}
private fun handleAllAlbumData(result: List<LocalMediaFolder>) {
if (ActivityCompatHelper.isDestroy(mActivity)) return
if (result.isNotEmpty()) {
val firstFolder = result[0]
SelectedManager.setCurrentLocalMediaFolder(firstFolder)
binding.tvTitle.text = StringUtils.getString(R.string.album)
albumListPopWindow.bindAlbumData(result)
binding.recyclerView.isEnabledLoadMore = true
} else {
showDataNull()
}
}
override fun loadFirstPageMediaData(firstBucketId: Long) {
mPage = 1
binding.recyclerView.isEnabledLoadMore = true
mLoader.loadPageMediaData(firstBucketId, mPage, mPage * config.pageSize,
object : OnQueryDataResultListener<LocalMedia>() {
override fun onComplete(result: ArrayList<LocalMedia>, isHasMore: Boolean) {
handleFirstPageMedia(result, isHasMore)
}
})
}
private fun handleFirstPageMedia(result: ArrayList<LocalMedia>, isHasMore: Boolean) {
if (ActivityCompatHelper.isDestroy(mActivity)) return
binding.recyclerView.isEnabledLoadMore = isHasMore
if (binding.recyclerView.isEnabledLoadMore && result.size == 0) {
// 如果isHasMore为true但result.size() = 0;
// 那么有可能是开启了某些条件过滤,实际上是还有更多资源的再强制请求
onRecyclerViewPreloadMore()
} else {
setAdapterData(result)
}
}
override fun loadOnlyInAppDirectoryAllMediaData() {
mLoader.loadOnlyInAppDirAllMedia { folder -> handleInAppDirAllMedia(folder) }
}
private fun handleInAppDirAllMedia(folder: LocalMediaFolder?) {
if (!ActivityCompatHelper.isDestroy(mActivity)) {
val sandboxDir = config.sandboxDir
val isNonNull = folder != null
val folderName = if (isNonNull) folder!!.folderName else File(sandboxDir).name
binding.tvTitle.text = folderName
if (isNonNull) {
SelectedManager.setCurrentLocalMediaFolder(folder)
setAdapterData(folder!!.data)
} else {
showDataNull()
}
}
}
/**
* 加载更多
*/
override fun loadMoreMediaData() {
if (binding.recyclerView.isEnabledLoadMore) {
mPage++
val localMediaFolder = SelectedManager.getCurrentLocalMediaFolder()
val bucketId = localMediaFolder?.bucketId ?: 0
mLoader.loadPageMediaData(bucketId, mPage, config.pageSize,
object : OnQueryDataResultListener<LocalMedia>() {
override fun onComplete(result: ArrayList<LocalMedia>, isHasMore: Boolean) {
handleMoreMediaData(result, isHasMore)
}
})
}
}
/**
* 处理加载更多的数据
*/
@SuppressLint("NotifyDataSetChanged")
private fun handleMoreMediaData(result: ArrayList<LocalMedia>, isHasMore: Boolean) {
if (ActivityCompatHelper.isDestroy(mActivity)) return
binding.recyclerView.isEnabledLoadMore = isHasMore
if (binding.recyclerView.isEnabledLoadMore) {
removePageCameraRepeatData(result.toMutableList())
if (result.isNotEmpty()) {
// val positionStart: Int = mAdapter.data.size
mAdapter.data.addAll(result)
mAdapter.notifyDataSetChanged()
if (mAdapter.data.isEmpty()) {
showDataNull()
} else {
hideDataNull()
}
} else {
// 如果没数据这里在强制调用一下上拉加载更多,防止是因为某些条件过滤导致的假为0的情况
onRecyclerViewPreloadMore()
}
if (result.size < PictureConfig.MIN_PAGE_SIZE) {
// 当数据量过少时强制触发一下上拉加载更多,防止没有自动触发加载更多
binding.recyclerView.onScrolled(binding.recyclerView.scrollX,
binding.recyclerView.scrollY)
}
}
}
private fun removePageCameraRepeatData(result: MutableList<LocalMedia>) {
try {
if (config.isPageStrategy) {
val iterator = result.iterator()
while (iterator.hasNext()) {
if (mAdapter.data.contains(iterator.next())) {
iterator.remove()
}
}
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
}
}
private fun handleSwitchAlbum(result: ArrayList<LocalMedia>, isHasMore: Boolean) {
if (ActivityCompatHelper.isDestroy(mActivity)) return
binding.recyclerView.isEnabledLoadMore = isHasMore
if (result.size == 0) {
// 如果从MediaStore拉取都没有数据了,adapter里的可能是缓存所以也清除
mAdapter.data.clear()
}
setAdapterData(result)
binding.recyclerView.onScrolled(0, 0)
binding.recyclerView.smoothScrollToPosition(0)
}
private fun setAdapterData(result: ArrayList<LocalMedia>) {
mAdapter.setList(result)
SelectedManager.clearAlbumDataSource()
SelectedManager.clearDataSource()
if (mAdapter.data.isEmpty()) {
showDataNull()
} else {
hideDataNull()
}
if (isFirstLoadData && mAdapter.data.isNotEmpty()) {
isFirstLoadData = false
val delayMills = if (RomUtils.isSamsung()) ALERT_LAYOUT_TRANSLATION_DELAY else 0L
mHandler.postDelayed({ mAdapter.notifyItemChanged(0, NOTIFY_DATA_CHANGE) }, delayMills)
}
}
以上就不多说了,先是权限判断,然后加载数据源,处理数据。
/**
* 设置选中
*
* @param isChecked
*/
fun selectedMedia(view: View, ivPicture: AppCompatImageView, isChecked: Boolean) {
if (view.isSelected != isChecked) {
view.isSelected = isChecked
}
if (isChecked) {
val selectColorFilter = StyleUtils.getColorFilter(view.context, R.color.color_80222229)
ivPicture.colorFilter = selectColorFilter
} else {
val defaultColorFilter = StyleUtils.getColorFilter(view.context, R.color.transparent)
ivPicture.colorFilter = defaultColorFilter
}
}
/**
* 检查LocalMedia是否被选中
*
* @param currentMedia
* @return
*/
fun isSelected(currentMedia: LocalMedia): Boolean {
val selectedResult: List<LocalMedia> = SelectedManager.getSelectedResult()
val isSelected = selectedResult.contains(currentMedia)
if (isSelected) {
val compare = currentMedia.compareLocalMedia
if (compare != null && compare.isEditorImage) {
currentMedia.cutPath = compare.cutPath
currentMedia.isCut = !TextUtils.isEmpty(compare.cutPath)
currentMedia.isEditorImage = compare.isEditorImage
}
}
return isSelected
}
/**
* 对选择数量进行编号排序
*/
fun notifySelectNumberStyle(tvNumber: AppCompatTextView, item: LocalMedia) {
tvNumber.text = ""
for (i in 0 until SelectedManager.getSelectCount()) {
val media = SelectedManager.getSelectedResult()[i]
if (TextUtils.equals(media.path, item.path) || media.id == item.id) {
item.num = media.num
media.setPosition(item.getPosition())
tvNumber.text = item.num.toString()
}
}
tvNumber.setBackgroundResource(if (tvNumber.text == "") R.drawable.shape_photo_album_num_bg else R.drawable.shape_photo_album_text_num_select_bg)
// 如果大于两位数 那么字体缩小 不然显示不下
tvNumber.textSize = if (item.num > 99) 10f else 13f
}
/**
* 计算选中数量
*/
private fun notifySelectNumber() {
val count = SelectedManager.getSelectCount()
binding.tvSelectNum.text = if (count > 0) {
StringUtils.getString(R.string.selected_picture, count)
} else {
StringUtils.getString(R.string.not_select)
}
if (count > 0) {
binding.tvSelectNum.setCompoundDrawablesWithIntrinsicBounds(
0,
0,
R.drawable.icon_nav_back_black,
0
)
binding.tvSelectNum.setTextColor(
ContextCompat.getColor(
mActivity,
R.color.color_323233
)
)
binding.tvNumber.text = count.toString()
binding.tvNumber.textSize = if (count > 99) 11f else 14f
// 如果没显示的时候 才会弹出动画
if (!binding.clBottomSendLayout.isVisible) {
//设置动画,从下向上滑动 布局
val translateAnimationLayout = TranslateAnimation(
TranslateAnimation.RELATIVE_TO_SELF, 0f,
TranslateAnimation.RELATIVE_TO_SELF, 0f,
TranslateAnimation.RELATIVE_TO_SELF, 1f,
TranslateAnimation.RELATIVE_TO_SELF, 0f
)
translateAnimationLayout.duration = 360 //设置动画的过渡时间
binding.clBottomSendLayout.startAnimation(translateAnimationLayout)
binding.clBottomSendLayout.animation.setAnimationListener(object :
AnimationListener {
override fun onAnimationStart(animation: Animation?) {
binding.clBottomSendLayout.visible()
}
override fun onAnimationEnd(animation: Animation?) {
binding.clBottomSendLayout.clearAnimation()
}
override fun onAnimationRepeat(animation: Animation?) {}
})
}
} else {
binding.tvSelectNum.setTextColor(
ContextCompat.getColor(
mActivity,
R.color.color_969799
)
)
binding.tvSelectNum.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
binding.tvNumber.text = ""
//设置动画,从上向下滑动 布局
val translateAnimationLayout = TranslateAnimation(
TranslateAnimation.RELATIVE_TO_SELF, 0f,
TranslateAnimation.RELATIVE_TO_SELF, 0f,
TranslateAnimation.RELATIVE_TO_SELF, 0f,
TranslateAnimation.RELATIVE_TO_SELF, 1f
)
translateAnimationLayout.duration = 360 //设置动画的过渡时间
binding.clBottomSendLayout.startAnimation(translateAnimationLayout)
binding.clBottomSendLayout.animation.setAnimationListener(object : AnimationListener {
override fun onAnimationStart(animation: Animation?) {
binding.clBottomSendLayout.invisible()
}
override fun onAnimationEnd(animation: Animation?) {
binding.clBottomSendLayout.clearAnimation()
binding.clBottomSendLayout.invisible()
binding.emotionContainerFrameLayout.gone()
KeyboardUtils.hideSoftInput(binding.layoutBottomEdit.editText)
}
override fun onAnimationRepeat(animation: Animation?) {}
})
binding.clBottomSendLayout.invisible()
}
}
private fun confirmSelect(currentMedia: LocalMedia, isSelected: Boolean): Int {
// val checkSelectValidity = isCheckSelectValidity(currentMedia, isSelected)
// if (checkSelectValidity != SelectedManager.SUCCESS) {
// return SelectedManager.INVALID
// }
// 先做选中判断逻辑 在执行以下 能否选中 在这里判断
val selectedResult: MutableList<LocalMedia> = SelectedManager.getSelectedResult()
val resultCode: Int
if (isSelected) {
selectedResult.remove(currentMedia)
resultCode = SelectedManager.REMOVE
} else {
selectedResult.add(currentMedia)
currentMedia.num = selectedResult.size
resultCode = SelectedManager.ADD_SUCCESS
}
onSelectedChange(resultCode == SelectedManager.ADD_SUCCESS, currentMedia)
return resultCode
}
fun onSelectedChange(isAddRemove: Boolean, currentMedia: LocalMedia) {
notifySelectNumber()
mAdapter.notifyItemChanged(currentMedia.position, NOTIFY_DATA_CHANGE)
if (!isAddRemove) {
sendChangeSubSelectPositionEvent()
}
}
选中的逻辑处理,Tegegram选中之后是有一个缩放的效果的,我这目前需求没有,就只加上一层遮罩,然后处理选中的数字排序,但由于用的是pictureselector库,那里面也有已经处理完的数字排序,就没有重复造轮子了,网上也是一搜一堆的,我就直接用库自带的了;然后设置一下字体大小,如果超过两位数的话那么重新设置一下,不然显示不下了。
//<editor-fold desc="相机事件回调处理">
// *********************** 相机事件回调处理 ***********************
/**
* 相机事件回调处理
*/
fun dispatchHandleCamera(intent: Intent?) {
ForegroundService.stopService(mActivity)
PictureThreadUtils.executeByIo(object : PictureThreadUtils.SimpleTask<LocalMedia?>() {
override fun doInBackground(): LocalMedia? {
val outputPath = getOutputPath(intent)
if (!TextUtils.isEmpty(outputPath)) {
config.cameraPath = outputPath
}
if (TextUtils.isEmpty(config.cameraPath)) {
return null
}
if (config.chooseMode == SelectMimeType.ofAudio()) {
copyOutputAudioToDir()
}
return buildLocalMedia(config.cameraPath)
}
override fun onSuccess(result: LocalMedia?) {
PictureThreadUtils.cancel(this)
if (result != null) {
onScannerScanFile(result)
dispatchCameraMediaResult(result)
}
}
})
}
/**
* 尝试匹配查找自定义相机返回的路径
*
* @param data
* @return
*/
private fun getOutputPath(data: Intent?): String? {
if (data == null) return null
// var outPutUri = data.getParcelableExtra<Uri>(MediaStore.EXTRA_OUTPUT)
val url = data.getStringExtra("url")
// if (config.chooseMode == SelectMimeType.ofAudio() && outPutUri == null) {
// outPutUri = data.data
// }
// if (outPutUri == null) {
// return null
// }
return if (PictureMimeType.isContent(url.toString())) url.toString() else Uri.parse(url)
.toString()
// return if (PictureMimeType.isContent(outPutUri.toString())) outPutUri.toString() else outPutUri.path
}
/**
* 刷新相册
*
* @param media 要刷新的对象
*/
private fun onScannerScanFile(media: LocalMedia) {
if (ActivityCompatHelper.isDestroy(mActivity)) return
if (SdkVersionUtils.isQ()) {
if (PictureMimeType.isHasVideo(media.mimeType) && PictureMimeType.isContent(config.cameraPath)) {
PictureMediaScannerConnection(mActivity, media.realPath)
}
} else {
val path =
if (PictureMimeType.isContent(config.cameraPath)) media.realPath else config.cameraPath
PictureMediaScannerConnection(mActivity, path)
if (PictureMimeType.isHasImage(media.mimeType)) {
val dirFile = File(path)
val lastImageId = MediaUtils.getDCIMLastImageId(mActivity, dirFile.parent)
if (lastImageId != -1) {
MediaUtils.removeMedia(mActivity, lastImageId)
}
}
}
}
/**
* buildLocalMedia
*
* @param absolutePath
*/
private fun buildLocalMedia(absolutePath: String?): LocalMedia {
val media: LocalMedia = LocalMedia.generateLocalMedia(mActivity, absolutePath)
media.chooseModel = config.chooseMode
if (SdkVersionUtils.isQ() && !PictureMimeType.isContent(absolutePath)) {
media.sandboxPath = absolutePath
} else {
media.sandboxPath = null
}
if (config.isCameraRotateImage && PictureMimeType.isHasImage(media.mimeType)) {
BitmapUtils.rotateImage(mActivity, absolutePath)
}
return media
}
/**
* copy录音文件至指定目录
*/
private fun copyOutputAudioToDir() {
try {
if (!TextUtils.isEmpty(config.outPutAudioDir) && PictureMimeType.isContent(config.cameraPath)) {
val inputStream =
PictureContentResolver.getContentResolverOpenInputStream(
mActivity,
Uri.parse(config.cameraPath)
)
val audioFileName: String = if (TextUtils.isEmpty(config.outPutAudioFileName)) {
""
} else {
if (config.isOnlyCamera) config.outPutAudioFileName else System.currentTimeMillis()
.toString() + "_" + config.outPutAudioFileName
}
val outputFile = PictureFileUtils.createCameraFile(
mActivity,
config.chooseMode, audioFileName, "", config.outPutAudioDir
)
val outputStream = FileOutputStream(outputFile.absolutePath)
val isCopyStatus = PictureFileUtils.writeFileFromIS(inputStream, outputStream)
if (isCopyStatus) {
MediaUtils.deleteUri(mActivity, config.cameraPath)
config.cameraPath = outputFile.absolutePath
}
}
} catch (e: FileNotFoundException) {
e.printStackTrace()
}
}
private fun dispatchCameraMediaResult(media: LocalMedia) {
val exitsTotalNum = albumListPopWindow.firstAlbumImageCount
if (!isAddSameImp(exitsTotalNum)) {
mAdapter.data.add(0, media)
}
// 进入下面 永远等于false
if (config.selectionMode == SelectModeConfig.SINGLE && config.isDirectReturnSingle) {
SelectedManager.clearSelectResult()
val selectResultCode = confirmSelect(media, false)
if (selectResultCode == SelectedManager.ADD_SUCCESS) {
dispatchTransformResult()
}
} else {
confirmSelect(media, false)
}
mAdapter.notifyItemInserted(if (mAdapter.isDisplayCamera()) 1 else 0)
mAdapter.notifyItemRangeChanged(if (mAdapter.isDisplayCamera()) 1 else 0,
mAdapter.getDefItemCountDataSize())
if (config.isOnlySandboxDir) {
var currentLocalMediaFolder = SelectedManager.getCurrentLocalMediaFolder()
if (currentLocalMediaFolder == null) {
currentLocalMediaFolder = LocalMediaFolder()
}
currentLocalMediaFolder.bucketId = ValueOf.toLong(media.parentFolderName.hashCode())
currentLocalMediaFolder.folderName = media.parentFolderName
currentLocalMediaFolder.firstMimeType = media.mimeType
currentLocalMediaFolder.firstImagePath = media.path
currentLocalMediaFolder.folderTotalNum = mAdapter.data.size
currentLocalMediaFolder.currentDataPage = mPage
currentLocalMediaFolder.isHasMore = false
val data = ArrayList(mAdapter.data)
currentLocalMediaFolder.data = data
binding.recyclerView.isEnabledLoadMore = false
SelectedManager.setCurrentLocalMediaFolder(currentLocalMediaFolder)
} else {
mergeFolder(media)
}
allFolderSize = 0
}
/**
* 拍照出来的合并到相应的专辑目录中去
*
* @param media
*/
private fun mergeFolder(media: LocalMedia) {
val allFolder: LocalMediaFolder
val albumList = albumListPopWindow.albumList
if (albumListPopWindow.folderCount == 0) {
// 1、没有相册时需要手动创建相机胶卷
allFolder = LocalMediaFolder()
val folderName: String = if (TextUtils.isEmpty(config.defaultAlbumName)) {
if (config.chooseMode == SelectMimeType.ofAudio())
StringUtils.getString(com.luck.picture.lib.R.string.ps_all_audio)
else
StringUtils.getString(com.luck.picture.lib.R.string.ps_camera_roll)
} else {
config.defaultAlbumName
}
allFolder.folderName = folderName
allFolder.firstImagePath = ""
allFolder.bucketId = PictureConfig.ALL.toLong()
albumList.add(0, allFolder)
} else {
// 2、有相册就找到对应的相册把数据加进去
allFolder = albumListPopWindow.getFolder(0)
}
allFolder.firstImagePath = media.path
allFolder.firstMimeType = media.mimeType
val data = ArrayList(mAdapter.data)
allFolder.data = data
allFolder.bucketId = PictureConfig.ALL.toLong()
allFolder.folderTotalNum =
if (isAddSameImp(allFolder.folderTotalNum)) allFolder.folderTotalNum else allFolder.folderTotalNum + 1
val currentLocalMediaFolder = SelectedManager.getCurrentLocalMediaFolder()
if (currentLocalMediaFolder == null || currentLocalMediaFolder.folderTotalNum == 0) {
SelectedManager.setCurrentLocalMediaFolder(allFolder)
}
// 先查找Camera目录,没有找到则创建一个Camera目录
var cameraFolder: LocalMediaFolder? = null
for (i in albumList.indices) {
val exitsFolder = albumList[i]
if (TextUtils.equals(exitsFolder.folderName, media.parentFolderName)) {
cameraFolder = exitsFolder
break
}
}
if (cameraFolder == null) {
// 还没有这个目录,创建一个
cameraFolder = LocalMediaFolder()
albumList.add(cameraFolder)
}
cameraFolder.folderName = media.parentFolderName
if (cameraFolder.bucketId == -1L || cameraFolder.bucketId == 0L) {
cameraFolder.bucketId = media.bucketId
}
// 分页模式下,切换到Camera目录下时,会直接从MediaStore拉取
if (config.isPageStrategy) {
cameraFolder.isHasMore = true
} else {
// 非分页模式数据都是存在目录的data下,所以直接添加进去就行
if (!isAddSameImp(allFolder.folderTotalNum)
|| !TextUtils.isEmpty(config.outPutCameraDir)
|| !TextUtils.isEmpty(config.outPutAudioDir)
) {
cameraFolder.data.add(0, media)
}
}
cameraFolder.folderTotalNum =
if (isAddSameImp(allFolder.folderTotalNum)) cameraFolder.folderTotalNum else cameraFolder.folderTotalNum + 1
cameraFolder.firstImagePath = config.cameraPath
cameraFolder.firstMimeType = media.mimeType
albumListPopWindow.bindAlbumData(albumList)
}
/**
* 数量是否一致
*/
private fun isAddSameImp(totalNum: Int): Boolean {
return if (totalNum == 0) {
false
} else allFolderSize in 1 until totalNum
}
// </editor-fold>
也是没啥可说的,打开相机,处理相机回调数据;基本与pictureselector库一致
/**
* @param isDismissDialog 不管执行什么操作 如果为true 那么执行完操作 会关闭弹框 默认为false
*/
private fun dismissWithDelayRun(isDismissDialog: Boolean = false, block: (() -> Unit)? = null) {
if (KeyboardUtils.isSoftInputVisible(mActivity)) {
binding.emotionContainerFrameLayout.gone()
KeyboardUtils.hideSoftInput(binding.layoutBottomEdit.editText)
if (isDismissDialog) {
dismissDelayRun(block)
}
} else {
if (isDismissDialog) {
dismissDelayRun(block)
} else {
// 如果有选中的 二次提示弹框
if (SelectedManager.getSelectCount() > 0) {
val dialog = AlertDialog.Builder(activity)
dialog.setTitle("是否关闭dialog")
dialog.setMessage("是否放弃选择关闭dialog")
dialog.setNegativeButton("否") { d, _ ->
d.cancel()
}
dialog.setPositiveButton("是") { d, _ ->
d.dismiss()
dismissDelayRun(block)
}
dialog.show()
} else {
dismissDelayRun(block)
}
}
}
}
/**
* @param block 关闭弹框
*/
private fun dismissDelayRun(block: (() -> Unit)? = null) {
SelectedManager.clearDataSource()
SelectedManager.clearAlbumDataSource()
SelectedManager.clearSelectResult()
binding.nestedScrolling.setDoPerformAnyCallbacks(false)
// 在关闭消失之前 添加动画
dialog?.window?.setWindowAnimations(R.style.DialogBottomAnim)
// 关闭之前先把颜色设置透明 在执行关闭
dialog?.window?.statusBarColor = Color.TRANSPARENT
binding.nestedScrolling.setBackgroundColor(0)
mHandler.postDelayed({
if (block != null) {
block.invoke()
} else {
dismissAllowingStateLoss()
}
}, ALERT_LAYOUT_TRANSLATION_DELAY)
}
override fun onDestroyView() {
super.onDestroyView()
if (mDragSelectTouchListener != null) {
mDragSelectTouchListener!!.stopAutoScroll()
}
}
关闭DialogFragment,因为图片是可以添加说明的,所以需要先判断键盘,如果显示就先关闭键盘,再则判断是否有选中的图片,如果有的话 需要再次弹出一个确认提示框(这个图方便就快速写了一个,不标准,按照自己需求写就可以),如果确认关闭那么就清除选中的数据缓冲,添加一个DialogFragment的关闭动画。在销毁的时候把初始化长按滚动选中释放掉。
以上基本就是选中媒体图片的弹框了,我的需求查看图片跟Telegram的有点不太一样,还有查看预览的图文混排,目前注释掉了,如果后续有时间的话我会补上,会有一个图文混排和图片预览的介绍。
git地址
网友评论