Compare commits

...

13 Commits

Author SHA1 Message Date
d146726b34 feat: 重新实现简单的图片裁切功能
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-22 17:29:34 +08:00
1a8efaa313 feat: 基本完成图片选择功能
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-21 15:43:44 +08:00
5e698fa497 feat: 完善快速选择列表
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-20 17:59:43 +08:00
7ba5f7239d feat: 完成基础功能
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-20 16:51:52 +08:00
f7489be9a4 feat: 完成基础逻辑
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-20 15:34:40 +08:00
5c326b7150 feat: 简单对接图片选择联动
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-19 18:11:12 +08:00
6cfff8237a feat: 把图片选择页面和预览页面合并到同一个 Activity 里实现
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-16 17:45:54 +08:00
c425198d47 feat: 预览图片页面实现渐出动画
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-16 14:32:12 +08:00
6a5d081f43 feat: 预览实现改为非 fragment 形式
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-16 10:54:13 +08:00
a9277f7925 feat: 替换预览实现的 fragment item
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-15 15:16:49 +08:00
4f59c10ce5 feat: 图片预览支持非选中部分
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-14 17:32:41 +08:00
32658b1db0 feat: 调整预览图片的页面实现
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-14 14:10:53 +08:00
6213ebdb88 feat: 将全局调用 Intent.ACTION_PICK 唤起系统图片选择的 intent 改为唤起本地图片选择 intent
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-08-12 16:38:34 +08:00
73 changed files with 2775 additions and 934 deletions

View File

@ -462,9 +462,6 @@
android:name="com.gh.gamecenter.video.game.GameVideoActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.gh.gamecenter.qa.editor.LocalMediaActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.gh.gamecenter.servers.GameServersActivity"

View File

@ -21,6 +21,8 @@ import com.gh.common.view.RichEditor
import com.gh.gamecenter.CropImageActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.entity.LocalVideoEntity
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.runOnIoThread
@ -30,6 +32,7 @@ import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.qa.editor.*
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.qa.entity.EditorInsertEntity
import com.gh.gamecenter.video.poster.PosterEditActivity
import com.gh.gamecenter.video.upload.UploadManager
@ -502,7 +505,7 @@ abstract class BaseRichEditorActivity<VM : BaseRichEditorViewModel> constructor(
startActivityForResult(
LocalMediaActivity.getIntent(
this@BaseRichEditorActivity,
LocalMediaActivity.ChooseType.VIDEO,
ChooseType.VIDEO,
maxChooseCount,
if (mtaEventName() == "提问帖") "发提问帖" else "发帖子"
), INSERT_MEDIA_VIDEO_CODE
@ -531,7 +534,7 @@ abstract class BaseRichEditorActivity<VM : BaseRichEditorViewModel> constructor(
val maxChooseCount = if (imageCount + 10 <= MAX_IMAGE_COUNT) 10 else MAX_IMAGE_COUNT - imageCount
val intent = LocalMediaActivity.getIntent(
this@BaseRichEditorActivity,
LocalMediaActivity.ChooseType.IMAGE,
ChooseType.IMAGE,
maxChooseCount,
if (mtaEventName() == "提问帖") "发提问帖" else "发帖子"
)

View File

@ -20,7 +20,7 @@ import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.entity.ForumDetailEntity
import com.gh.gamecenter.entity.LocalVideoEntity
import com.gh.gamecenter.common.entity.LocalVideoEntity
import com.gh.gamecenter.entity.QuoteCountEntity
import com.gh.gamecenter.qa.BbsType
import com.gh.gamecenter.retrofit.RetrofitManager
@ -39,8 +39,6 @@ import retrofit2.HttpException
import java.io.File
import java.io.FileOutputStream
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.LinkedHashMap
import kotlin.collections.set

View File

@ -2,23 +2,19 @@ package com.gh.gamecenter;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.gh.gamecenter.common.base.activity.ToolBarActivity;
import com.gh.gamecenter.common.utils.BitmapUtils;
import com.gh.gamecenter.common.base.activity.BaseActivity;
import com.gh.gamecenter.core.utils.DisplayUtils;
import com.gh.gamecenter.common.constant.EntranceConsts;
import com.gh.gamecenter.common.view.CropImageCustom;
import com.gh.gamecenter.core.utils.SimpleImageLoader;
import com.github.piasy.biv.view.BigImageView;
import java.io.File;
import java.lang.ref.SoftReference;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
@ -26,14 +22,10 @@ import androidx.annotation.NonNull;
/**
* 裁剪图片
*/
public class CropImageActivity extends ToolBarActivity {
protected CropImageCustom mCropImageCustom;
public class CropImageActivity extends BaseActivity {
public static final String RESULT_CLIP_PATH = "result_clip_path";
private SoftReference<Bitmap> reference;
protected boolean mBlackTheme = false;
@NonNull
@ -67,74 +59,50 @@ public class CropImageActivity extends ToolBarActivity {
mBlackTheme = getIntent().getBooleanExtra(EntranceConsts.KEY_BLACK_THEME, false);
super.onCreate(savedInstanceState);
mCropImageCustom = findViewById(R.id.cropimage_custom);
View statusBarView = findViewById(R.id.status_bar);
mTitleTv.setTextColor(mBlackTheme ? Color.WHITE : Color.BLACK);
mToolbar.setBackgroundColor(getResources().getColor(mBlackTheme ? R.color.text_28282E : R.color.white));
statusBarView.setBackgroundColor(getResources().getColor(mBlackTheme ? R.color.text_28282E : R.color.white));
setNavigationTitle(getString(R.string.title_crop_image));
setToolbarMenu(R.menu.menu_positive);
MenuItem menuItem = getMenuItem(R.id.layout_menu_positive);
TextView menuButton = menuItem.getActionView().findViewById(R.id.menu_answer_post);
menuButton.setTextColor(getResources().getColor(R.color.text_theme));
BigImageView iv = findViewById(R.id.iv);
iv.showImage(Uri.parse(getIntent().getStringExtra(EntranceConsts.KEY_PATH)));
iv.setInitScaleType(BigImageView.INIT_SCALE_TYPE_FIT_XY);
iv.setImageLoaderCallback(new SimpleImageLoader() {
@Override
public void onSuccess(File image) {
super.onSuccess(image);
iv.getSSIV().setMinimumDpi(80);
}
});
float ratio = getIntent().getFloatExtra(EntranceConsts.KEY_IMAGE_CROP_RATIO, 1F);
mCropImageCustom.setCropRatio(ratio);
int assistRes = getIntent().getIntExtra(EntranceConsts.KEY_ASSIST_RES, -1);
if (assistRes > 0) {
View view = LayoutInflater.from(this).inflate(assistRes, null, false);
addAssistView(view);
}
DisplayUtils.setLightStatusBar(this, !mBlackTheme);
DisplayUtils.setStatusBarColor(this, R.color.transparent, !mBlackTheme);
findViewById(R.id.pieceMediaControl).setBackgroundResource(R.color.transparent);
TextView confirmTv = findViewById(R.id.confirmTv);
confirmTv.setOnClickListener(v -> {
submitResultAndFinish();
});
confirmTv.setEnabled(true);
confirmTv.setAlpha(1F);
TextView previewTv = findViewById(R.id.previewTv);
previewTv.setText(R.string.cancel);
previewTv.setOnClickListener(v -> finish());
DisplayUtils.transparentStatusBar(this);
}
@Override
public int provideNavigationIcon() {
return mBlackTheme ? R.drawable.ic_toolbar_back_white : R.drawable.ic_bar_back;
}
public void submitResultAndFinish() {
Intent data = new Intent();
String clipPath = getCacheDir().getAbsolutePath() + File.separator + System.currentTimeMillis() + ".jpg";
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.layout_menu_positive) {
Intent data = new Intent();
String clipPath = getCacheDir().getAbsolutePath() + File.separator + System.currentTimeMillis() + ".jpg";
mCropImageCustom.savePicture(clipPath);
data.putExtra(RESULT_CLIP_PATH, clipPath);
setResult(RESULT_OK, data);
finish();
}
return super.onMenuItemClick(item);
data.putExtra(RESULT_CLIP_PATH, clipPath);
setResult(RESULT_OK, data);
finish();
}
public void addAssistView(View view) {
mCropImageCustom.addAssistView(view);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (reference != null && reference.get() != null) {
reference.get().recycle();
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus && (reference == null || reference.get() == null)) {
ImageView imageView = mCropImageCustom.getCropImageZoomView();
Bitmap bitmap = BitmapUtils.getBitmapByFile(getIntent().getStringExtra(EntranceConsts.KEY_PATH),
imageView.getWidth(), imageView.getHeight());
if (bitmap != null) {
reference = new SoftReference<>(bitmap);
imageView.setImageBitmap(reference.get());
}
}
}
}

View File

@ -3,7 +3,6 @@ package com.gh.gamecenter.game.upload
import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import android.provider.MediaStore
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.TextPaint
@ -29,18 +28,21 @@ import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
import com.gh.gamecenter.common.callback.OnListClickListener
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.choosepic.ChoosePicAdapter
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.FragmentGameUploadBinding
import com.gh.gamecenter.feature.entity.InstallGameEntity
import com.gh.gamecenter.feature.game.SelectGameAdapter
import com.gh.gamecenter.feature.selector.ChooseType
import com.halo.assistant.HaloApp
import com.lightgame.utils.Util_System_Keyboard
import com.lightgame.utils.Utils
import com.zhihu.matisse.Matisse
import com.zhihu.matisse.internal.utils.PathUtils
import io.reactivex.disposables.Disposable
import okhttp3.MediaType
import okhttp3.RequestBody
@ -86,7 +88,6 @@ class GameUploadFragment : ToolbarFragment() {
mViewModel.upLoadSuccess.observe(viewLifecycleOwner, Observer {
if (it) {
ToastUtils.showToast("上传成功")
MtaHelper.onEvent("游戏上传", "游戏上传", "上传成功")
mUploadDialog.dismiss()
requireActivity().finish()
} else {
@ -119,17 +120,11 @@ class GameUploadFragment : ToolbarFragment() {
5,
object : OnListClickListener {
override fun <T : Any?> onListClick(view: View?, position: Int, data: T) {
MtaHelper.onEvent("游戏上传", "游戏图片", "添加图片")
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
val intent = LocalMediaActivity.getIntent(requireContext(), ChooseType.IMAGE, 1, "游戏上传")
startActivityForResult(intent, MEDIA_STORE_REQUEST)
}
}
) {
MtaHelper.onEvent(
"游戏上传",
"游戏图片",
"删除图片"
)
}
mAdapter?.setPicItem(R.layout.game_upload_pic_item)
mAdapter?.setSuggestAddPicIcon(R.drawable.icon_pic_add)
@ -147,19 +142,13 @@ class GameUploadFragment : ToolbarFragment() {
1,
object : OnListClickListener {
override fun <T : Any?> onListClick(view: View?, position: Int, data: T) {
MtaHelper.onEvent("游戏上传", "游戏图标", "添加图片")
PermissionHelper.checkStoragePermissionBeforeAction(requireActivity()) {
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
val intent = LocalMediaActivity.getIntent(requireContext(), ChooseType.IMAGE, 1, "游戏上传")
startActivityForResult(intent, MEDIA_ICON_STORE_REQUEST)
}
}
}
) {
MtaHelper.onEvent(
"游戏上传",
"游戏图标",
"删除图片"
)
}
mIconAdapter?.setPicItem(R.layout.game_upload_pic_item)
mIconAdapter?.setSuggestAddPicIcon(R.drawable.icon_pic_add)
@ -194,18 +183,15 @@ class GameUploadFragment : ToolbarFragment() {
private fun initListener() {
mBinding.chooseGameLl.setOnClickListener {
MtaHelper.onEvent("游戏上传", "安装包", "点我选择")
showSelectDialog()
}
mBinding.gameIsNetworkingRg.setOnCheckedChangeListener { _, checkedId ->
when (checkedId) {
R.id.gameNetworkingRb -> {
mIsOnline = "yes"
MtaHelper.onEvent("游戏上传", "是否联网", "需要联网")
}
R.id.gameNoNetworkingRb -> {
mIsOnline = "no"
MtaHelper.onEvent("游戏上传", "是否联网", "无需联网")
}
}
}
@ -213,15 +199,12 @@ class GameUploadFragment : ToolbarFragment() {
when (checkedId) {
R.id.gameLanguageChineseRb -> {
mGameLanguage = "中文"
MtaHelper.onEvent("游戏上传", "游戏语言", "中文")
}
R.id.gameLanguageEnglishRb -> {
mGameLanguage = "英文"
MtaHelper.onEvent("游戏上传", "游戏语言", "英文")
}
R.id.gameLanguageOtherRb -> {
mGameLanguage = "其他"
MtaHelper.onEvent("游戏上传", "游戏语言", "其他")
}
}
}
@ -229,21 +212,17 @@ class GameUploadFragment : ToolbarFragment() {
when (checkedId) {
R.id.gameTypeLocalRb -> {
mGameType = "local"
MtaHelper.onEvent("游戏上传", "游戏类型", "单机")
}
R.id.gameTypeOnlineRb -> {
mGameType = "online"
MtaHelper.onEvent("游戏上传", "游戏类型", "网游")
}
R.id.gameTypeOtherRb -> {
mGameType = "other"
MtaHelper.onEvent("游戏上传", "游戏类型", "其他")
}
}
}
mBinding.addGameLabeTv.setOnClickListener {
MtaHelper.onEvent("游戏上传", "游戏标签", "添加标签")
if (mTags.size < mMaxTagSize) {
showAddTagDialog()
} else {
@ -256,7 +235,6 @@ class GameUploadFragment : ToolbarFragment() {
}
private fun commit() {
MtaHelper.onEvent("游戏上传", "提交", "提交")
if (TextUtils.isEmpty(mBinding.tvChoose.text.toString())) {
ToastUtils.showToast("请先选择游戏安装包")
return
@ -471,11 +449,9 @@ class GameUploadFragment : ToolbarFragment() {
}
back.setOnClickListener {
MtaHelper.onEvent("游戏上传", "安装包", "关闭")
mSelectGameDialog?.cancel()
}
manualBtn.setOnClickListener {
MtaHelper.onEvent("游戏上传", "安装包", "从设备上选择")
val intent = CleanApkActivity.getIntent(requireContext(), true)
PermissionHelper.checkManageAllFilesOrStoragePermissionBeforeAction(requireActivity()) {
startActivityForResult(intent, CHOOSE_LOCAL_APK)
@ -499,32 +475,20 @@ class GameUploadFragment : ToolbarFragment() {
super.onActivityResult(requestCode, resultCode, data)
if (data == null) return
if (requestCode == MEDIA_STORE_REQUEST || requestCode == MEDIA_ICON_STORE_REQUEST) {
val selectedImage = data.data ?: return
val filePathColumn = arrayOf(MediaStore.Images.Media.DATA)
val selectedPaths = Matisse.obtainResult(data) ?: return
val picturePath = PathUtils.getPath(requireContext(), selectedPaths[0])
val cursor = requireContext().contentResolver.query(selectedImage, filePathColumn, null, null, null)
?: return
cursor.moveToFirst()
Utils.log("picturePath = $picturePath")
try {
val columnIndex = cursor.getColumnIndex(filePathColumn[0])
val picturePath = cursor.getString(columnIndex)
cursor.close()
Utils.log("picturePath = $picturePath")
val file = File(picturePath)
if (file.length() > 5 * 1024 * 1024) {
ToastUtils.showToast(getString(R.string.pic_max_hint, 5))
val file = File(picturePath)
if (file.length() > 5 * 1024 * 1024) {
ToastUtils.showToast(getString(R.string.pic_max_hint, 5))
} else {
if (requestCode == MEDIA_STORE_REQUEST) {
mAdapter!!.addFileList(picturePath)
} else {
if (requestCode == MEDIA_STORE_REQUEST) {
mAdapter!!.addFileList(picturePath)
} else {
mIconAdapter!!.addFileList(picturePath)
}
mIconAdapter!!.addFileList(picturePath)
}
} catch (e: Exception) {
ToastUtils.showToast(e.message ?: "")
}
} else if (requestCode == CHOOSE_LOCAL_APK) {
val packageName = data.getStringExtra(EntranceConsts.KEY_PACKAGENAME) ?: ""
@ -610,7 +574,6 @@ class GameUploadFragment : ToolbarFragment() {
labelTv.text = tag
labelView.findViewById<View>(R.id.picDelIv).setOnClickListener {
if (mTags.contains(tag)) {
MtaHelper.onEvent("游戏上传", "游戏标签", "删除标签")
mBinding.gameLabelFl.removeView(labelView)
mTags.remove(tag)
mBinding.gameLabelFl.goneIf(mTags.isEmpty())

View File

@ -16,12 +16,13 @@ import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.common.utils.PermissionHelper
import com.gh.gamecenter.R
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.common.utils.setRootBackgroundDrawable
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.databinding.DialogChooseGameCollectionCoverTypeBinding
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.gamecollection.publish.GameCollectionEditActivity.Companion.REQUEST_CODE_IMAGE
import com.gh.gamecenter.qa.editor.LocalMediaActivity
import com.halo.assistant.HaloApp
class ChooseGameCollectionCoverTypeDialog : BaseDialogFragment() {
@ -42,7 +43,7 @@ class ChooseGameCollectionCoverTypeDialog : BaseDialogFragment() {
startActivityForResult(
LocalMediaActivity.getIntent(
requireContext(),
LocalMediaActivity.ChooseType.IMAGE,
ChooseType.IMAGE,
1,
"创建游戏单"
), REQUEST_CODE_IMAGE

View File

@ -10,20 +10,20 @@ import android.view.View
import androidx.annotation.RequiresApi
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import com.gh.common.util.*
import com.gh.gamecenter.CropImageActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
import com.gh.gamecenter.common.base.fragment.WaitingDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.entity.ErrorEntity
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.FragmentBackgroundPreviewBinding
import com.gh.gamecenter.feature.entity.BackgroundImageEntity
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserViewModel
import com.gh.gamecenter.qa.editor.LocalMediaActivity
import com.halo.assistant.HaloApp
import com.zhihu.matisse.Matisse
import io.reactivex.Single
@ -282,7 +282,7 @@ class BackgroundPreviewFragment : ToolbarFragment() {
startActivityForResult(
LocalMediaActivity.getIntent(
requireContext(),
LocalMediaActivity.ChooseType.IMAGE,
ChooseType.IMAGE,
1,
"个性背景"
), MEDIA_STORE_REQUEST

View File

@ -12,12 +12,13 @@ import com.gh.gamecenter.CropImageActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
import com.gh.gamecenter.common.base.fragment.WaitingDialogFragment
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.common.utils.PermissionHelper
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.common.view.GridSpacingItemColorDecoration
import com.gh.gamecenter.databinding.PersonalityBackgroundFragmentBinding
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.login.user.UserViewModel
import com.gh.gamecenter.qa.editor.LocalMediaActivity
import com.halo.assistant.HaloApp
import com.zhihu.matisse.Matisse
@ -75,7 +76,7 @@ class PersonalityBackgroundFragment : ToolbarFragment() {
startActivityForResult(
LocalMediaActivity.getIntent(
requireContext(),
LocalMediaActivity.ChooseType.IMAGE,
ChooseType.IMAGE,
1,
"个性背景"
), MEDIA_STORE_REQUEST

View File

@ -28,6 +28,7 @@ import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.baselist.ListFragment
import com.gh.gamecenter.common.constant.EntranceConsts.KEY_COMMENT_ID
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.common.syncpage.SyncDataEntity
import com.gh.gamecenter.common.syncpage.SyncFieldConstants
import com.gh.gamecenter.common.syncpage.SyncPageRepository
@ -38,10 +39,10 @@ import com.gh.gamecenter.databinding.ItemCommentEditImageBinding
import com.gh.gamecenter.eventbus.EBCommentSuccess
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.feature.eventbus.EBDeleteComment
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.qa.comment.CommentActivity.Companion.GAME_COLLECTION_ID
import com.gh.gamecenter.qa.comment.CommentActivity.Companion.GAME_COLLECTION_TITLE
import com.gh.gamecenter.qa.comment.CommentActivity.Companion.QUESTION_ID
import com.gh.gamecenter.qa.editor.LocalMediaActivity
import com.halo.assistant.HaloApp
import com.lightgame.utils.Util_System_Keyboard
import com.lightgame.utils.Utils
@ -494,7 +495,7 @@ open class NewCommentFragment : ListFragment<CommentEntity, NewCommentViewModel>
val maxChooseCount = 9 - mViewModel.pictureList.size
val intent = LocalMediaActivity.getIntent(
requireContext(),
LocalMediaActivity.ChooseType.IMAGE,
ChooseType.IMAGE,
maxChooseCount,
"评论列表"
)
@ -521,11 +522,6 @@ open class NewCommentFragment : ListFragment<CommentEntity, NewCommentViewModel>
return
}
if (mShowInputOnly) {
// Fuck pm
MtaHelper.onEvent("帖子详情", "评论详情-全部回复", "发送")
}
mViewModel.postPictureAndComment(content, mCommentEntity)
}

View File

@ -1,148 +0,0 @@
package com.gh.gamecenter.qa.editor
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.ImageView
import android.widget.PopupWindow
import androidx.core.content.ContextCompat
import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.updateStatusBarColor
import com.halo.assistant.HaloApp
import com.zhihu.matisse.Matisse
import com.zhihu.matisse.MimeType
import com.zhihu.matisse.internal.entity.Album
import com.zhihu.matisse.internal.entity.SelectionSpec
import com.zhihu.matisse.internal.model.AlbumCollection
/**
* 选择本地视频/图片
*/
class LocalMediaActivity : ToolBarActivity(), AlbumCollection.AlbumCallbacks {
private var mLocalMediaFragment: LocalMediaFragment? = null
private lateinit var mAlbumsSpinner: VideoAlbumsSpanner
private lateinit var mAlbumsAdapter: VideoAlbumsAdapter
private val mAlbumCollection = AlbumCollection()
private var mIsFirstAlbumLoad = true
private var mChooseType = ""
override fun getLayoutId(): Int = R.layout.activity_video_tablayout_viewpager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
updateStatusBarColor(R.color.ui_surface, R.color.ui_surface)
mChooseType = intent.getStringExtra(EntranceConsts.KEY_TYPE) ?: ""
if (mChooseType == ChooseType.VIDEO.value) {
setNavigationTitle("本地视频")
} else {
setNavigationTitle("本地图片")
}
mLocalMediaFragment = LocalMediaFragment().apply { arguments = intent.extras }
supportFragmentManager.beginTransaction()
.replace(R.id.container, mLocalMediaFragment!!, LocalMediaFragment::class.java.name)
.commitAllowingStateLoss()
initAlbumsSpinner()
mTitleTv.setOnClickListener {
mAlbumsSpinner.show(findViewById<View>(R.id.container).height)
setPhotoNavigationTitle(true)
}
}
private fun initAlbumsSpinner() {
mAlbumsSpinner = VideoAlbumsSpanner(this)
mAlbumsAdapter = VideoAlbumsAdapter(this)
mAlbumsSpinner.setPopupAnchorView(findViewById(R.id.normal_toolbar))
mAlbumsSpinner.setAdapter(mAlbumsAdapter)
mAlbumsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
mAlbumCollection.setStateCurrentSelection(position)
mAlbumsAdapter.cursor.moveToPosition(position)
val album = Album.valueOf(mAlbumsAdapter.cursor)
if (album.isAll && SelectionSpec.getInstance().capture) {
album.addCaptureCount()
}
if (mLocalMediaFragment?.isAdded == true) {
mLocalMediaFragment?.loadVideos(album)
}
}
}
mAlbumsSpinner.setDismissListener(PopupWindow.OnDismissListener {
setPhotoNavigationTitle(false)
})
//必须加这行代码,[SelectionSpec]是单例模式下次使用必须先更新MimeType
val mimeType = if (mChooseType == ChooseType.VIDEO.value) {
MimeType.ofVideo()
} else {
MimeType.ofImage()
}
val maxChooseCount = intent.getIntExtra(EntranceConsts.KEY_CHOOSE_MAX_COUNT, 1)
Matisse.from(this).choose(mimeType).showSingleMediaType(true).maxSelectable(maxChooseCount)
mAlbumCollection.onCreate(this, this)
mAlbumCollection.loadAlbums()
}
override fun onAlbumLoad(cursor: Cursor?) {
if (mIsFirstAlbumLoad) {
mIsFirstAlbumLoad = false
mAlbumsAdapter.swapCursor(cursor)
mBaseHandler.post {
cursor?.moveToPosition(mAlbumCollection.currentSelection)
val album = Album.valueOf(cursor)
if (album.isAll && SelectionSpec.getInstance().capture) {
album.addCaptureCount()
}
if (mLocalMediaFragment?.isAdded == true) {
mLocalMediaFragment?.loadVideos(album)
}
}
}
}
override fun onAlbumReset() {
}
private fun setPhotoNavigationTitle(up: Boolean) {
val drawable = ContextCompat.getDrawable(
HaloApp.getInstance().application,
if (up) R.drawable.ic_video_arrow_up else R.drawable.ic_video_arrow_down
)
val arrowIv = findViewById<ImageView>(R.id.arrowIv)
arrowIv?.setImageDrawable(drawable)
}
override fun isAutoResetViewBackgroundEnabled(): Boolean = true
override fun onDarkModeChanged() {
super.onDarkModeChanged()
updateStatusBarColor(R.color.ui_surface, R.color.ui_surface)
}
companion object {
fun getIntent(context: Context, chooseType: ChooseType, maxChooseCount: Int = 1, entrance: String): Intent {
return Intent(context, LocalMediaActivity::class.java).apply {
putExtra(EntranceConsts.KEY_TYPE, chooseType.value)
putExtra(EntranceConsts.KEY_CHOOSE_MAX_COUNT, maxChooseCount)
putExtra(EntranceConsts.KEY_ENTRANCE, entrance)
}
}
}
enum class ChooseType(val value: String) {
VIDEO("video"),
IMAGE("image")
}
}

View File

@ -1,95 +0,0 @@
package com.gh.gamecenter.qa.editor
import android.content.Context
import android.database.Cursor
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.common.util.*
import com.gh.gamecenter.R
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.databinding.LocalVideoItemBinding
import com.zhihu.matisse.internal.entity.Item
import com.zhihu.matisse.internal.ui.adapter.RecyclerViewCursorAdapter
import com.zhihu.matisse.internal.utils.PathUtils
class LocalMediaAdapter(
val context: Context,
val mChooseType: String,
val maxChooseSize: Int,
val entrance: String,
val callback: (ArrayList<Item>) -> Unit
) : RecyclerViewCursorAdapter<LocalVideoPreviewViewHolder>(null) {
private val mSelectedMediaList = arrayListOf<Item>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocalVideoPreviewViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.local_video_item, parent, false)
return LocalVideoPreviewViewHolder(LocalVideoItemBinding.bind(view))
}
override fun onBindViewHolder(holder: LocalVideoPreviewViewHolder, cursor: Cursor?, position: Int) {
val item = Item.valueOf(cursor)
holder.binding.durationTv.goneIf(mChooseType == LocalMediaActivity.ChooseType.IMAGE.value)
val path = "file:///${PathUtils.getPath(context, item.contentUri)}"
ImageUtils.displayResizeMedia(holder.binding.preview, path, 200, 200)
holder.binding.durationTv.text = TimeUtils.formatVideoDuration(item.duration / 1000)
val drawable = if (mSelectedMediaList.contains(item)) {
if (maxChooseSize == 1) {
R.drawable.ic_choose_media_selected.toDrawable()
} else {
R.drawable.ic_choose_media_bg.toDrawable()
}
} else {
R.drawable.ic_choose_media_normal.toDrawable()
}
holder.binding.checkImageView.setImageDrawable(drawable)
if (mSelectedMediaList.contains(item) && maxChooseSize > 1) {
holder.binding.chooseCountTv.visibility = View.VISIBLE
holder.binding.chooseCountTv.text = (mSelectedMediaList.indexOf(item) + 1).toString()
} else {
holder.binding.chooseCountTv.visibility = View.GONE
}
holder.itemView.setOnClickListener {
if (mSelectedMediaList.contains(item)) {
mSelectedMediaList.remove(item)
notifyDataSetChanged()
callback.invoke(mSelectedMediaList)
} else {
if (maxChooseSize == 1) {
mSelectedMediaList.clear()
}
if (mSelectedMediaList.size < maxChooseSize) {
mSelectedMediaList.add(item)
notifyDataSetChanged()
callback.invoke(mSelectedMediaList)
} else {
if (mChooseType == LocalMediaActivity.ChooseType.IMAGE.value) {
ToastUtils.showToast("至多选择${maxChooseSize}张图片")
} else {
ToastUtils.showToast("至多选择${maxChooseSize}条视频")
}
}
}
if (entrance == "发帖子" || entrance == "发提问帖" || entrance == "发视频帖") {
val publishContentType = if (entrance == "发帖子") "帖子" else if (entrance == "发提问帖") "提问帖" else "视频帖"
val publishMediaType = if (mChooseType == LocalMediaActivity.ChooseType.IMAGE.value) "图片" else "视频"
NewLogUtils.logChooseMedia("click_radio_button", publishContentType, publishMediaType)
}
}
}
override fun getItemViewType(position: Int, cursor: Cursor?): Int {
return 0
}
fun getSelectedMediaList(): ArrayList<Item> {
return mSelectedMediaList
}
}
class LocalVideoPreviewViewHolder(val binding: LocalVideoItemBinding) : BaseRecyclerViewHolder<Any>(binding.root)

View File

@ -1,191 +0,0 @@
package com.gh.gamecenter.qa.editor
import android.app.Activity
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.GridLayoutManager
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.tryWithDefaultCatch
import com.gh.gamecenter.common.view.GridSpacingItemDecoration
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.databinding.FragmentLocalMediaBinding
import com.gh.gamecenter.entity.LocalVideoEntity
import com.zhihu.matisse.internal.entity.Album
import com.zhihu.matisse.internal.entity.Item
import com.zhihu.matisse.internal.model.AlbumMediaCollection
import com.zhihu.matisse.internal.model.SelectedItemCollection
import com.zhihu.matisse.internal.ui.BasePreviewActivity
import com.zhihu.matisse.internal.ui.SelectedPreviewActivity
import com.zhihu.matisse.internal.utils.PathUtils
import com.zhihu.matisse.ui.MatisseActivity
class LocalMediaFragment : BaseFragment<Any>(), AlbumMediaCollection.AlbumMediaCallbacks {
private lateinit var mBinding: FragmentLocalMediaBinding
private lateinit var mAdapter: LocalMediaAdapter
private var mAlbumMediaCollection: AlbumMediaCollection? = null
private var mChooseType = ""
override fun getLayoutId(): Int = 0
override fun getInflatedLayout(): View {
mBinding = FragmentLocalMediaBinding.inflate(LayoutInflater.from(requireContext()), null, false)
return mBinding.root
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mChooseType = arguments?.getString(EntranceConsts.KEY_TYPE) ?: ""
// mBinding.reuseNoneData.reuseNoneDataTv.text = "暂无数据~"
mBinding.listRv.layoutManager = GridLayoutManager(requireContext(), 3)
mBinding.listRv.addItemDecoration(GridSpacingItemDecoration(3, 4F.dip2px(), false))
val maxChooseCount = arguments?.getInt(EntranceConsts.KEY_CHOOSE_MAX_COUNT, 1)
mAdapter = LocalMediaAdapter(
requireContext(), mChooseType, maxChooseCount
?: 1, mEntrance
) {
mBinding.previewTv.isEnabled = it.isNotEmpty()
mBinding.confirmTv.isEnabled = it.isNotEmpty()
if (it.isEmpty()) {
mBinding.previewTv.setTextColor(R.color.text_instance.toColor(requireContext()))
mBinding.confirmTv.alpha = 0.6f
} else {
mBinding.previewTv.setTextColor(R.color.text_secondary.toColor(requireContext()))
mBinding.confirmTv.alpha = 1f
}
mBinding.numTv.text = "(${it.size}/${mAdapter.maxChooseSize})"
}
mBinding.numTv.text = "(0/${mAdapter.maxChooseSize})"
mBinding.listRv.adapter = mAdapter
mBinding.listRefresh.isEnabled = false
val publishContentType = if (mEntrance == "发帖子") "帖子" else if (mEntrance == "发提问帖") "提问帖" else "视频帖"
val publishMediaType = if (mChooseType == LocalMediaActivity.ChooseType.IMAGE.value) "图片" else "视频"
mBinding.previewTv.setOnClickListener {
if (mChooseType == LocalMediaActivity.ChooseType.VIDEO.value) {
val intent = PreviewVideoActivity.getIntent(requireContext(), mAdapter.getSelectedMediaList())
requireActivity().startActivityForResult(intent, PREVIEW_VIDEO)
NewLogUtils.logChooseMedia("click_preview", publishContentType, publishMediaType)
} else {
val intent = Intent(requireContext(), SelectedPreviewActivity::class.java)
val bundle = bundleOf(
SelectedItemCollection.STATE_SELECTION to mAdapter.getSelectedMediaList(),
SelectedItemCollection.STATE_COLLECTION_TYPE to SelectedItemCollection.COLLECTION_IMAGE
)
intent.putExtra(BasePreviewActivity.EXTRA_DEFAULT_BUNDLE, bundle)
startActivityForResult(intent, PREVIEW_IMAGE)
}
}
mBinding.confirmTv.setOnClickListener {
NewLogUtils.logChooseMedia("click_confirm", publishContentType, publishMediaType)
val intent = Intent()
if (mChooseType == LocalMediaActivity.ChooseType.VIDEO.value) {
val localVideoList = arrayListOf<LocalVideoEntity>()
mAdapter.getSelectedMediaList().forEach {
val path = PathUtils.getPath(requireContext(), it.contentUri)
if (path == null) {
toast("视频已不存在,请重新选择")
return@forEach
}
val id = MD5Utils.getUrlMD5(path) + System.currentTimeMillis()
val format = getFileFormat(it.mimeType)
localVideoList.add(
LocalVideoEntity(
id,
path,
contentUri = it.contentUri,
duration = it.duration,
format = format,
size = it.size
)
)
}
intent.putExtra(LocalVideoEntity::class.java.name, localVideoList)
} else {
val data = mAdapter.getSelectedMediaList().map { it.contentUri }.toList()
val path = data.map { PathUtils.getPath(requireContext(), it) }.toList()
intent.putParcelableArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION, ArrayList<Uri>(data))
intent.putStringArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION_PATH, ArrayList<String>(path))
}
requireActivity().setResult(Activity.RESULT_OK, intent)
requireActivity().finish()
}
}
private fun getFileFormat(mimeType: String?): String {
var format = ""
tryWithDefaultCatch {
if (mimeType != null) {
val split = mimeType.split("/")
format = if (split.count() >= 2) {
split[1]
} else {
mimeType
}
}
}
return format
}
override fun onAlbumMediaReset() {
mAdapter.swapCursor(null)
}
override fun onAlbumMediaLoad(cursor: Cursor?) {
mAdapter.swapCursor(cursor)
mBinding.reuseNoneData.reuseNoneData.visibility = View.GONE
mBinding.reuseLlLoading.root.visibility = View.GONE
mBinding.reuseNoConnection.root.visibility = View.GONE
mBinding.listRefresh.isRefreshing = false
}
fun loadVideos(album: Album) {
mAlbumMediaCollection?.onDestroy()
mAlbumMediaCollection = AlbumMediaCollection()
mAlbumMediaCollection?.onCreate(requireActivity(), this@LocalMediaFragment)
mAlbumMediaCollection?.load(album)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (data == null) return
if (requestCode == PREVIEW_IMAGE) {
val bundleExtra = data.getBundleExtra(BasePreviewActivity.EXTRA_RESULT_BUNDLE)
val resultApply = data.getBooleanExtra(BasePreviewActivity.EXTRA_RESULT_APPLY, false)
val items = bundleExtra?.getParcelableArrayList<Item>(SelectedItemCollection.STATE_SELECTION)
if (resultApply && !items.isNullOrEmpty()) {
mAdapter.getSelectedMediaList().clear()
mAdapter.getSelectedMediaList().addAll(items)
mAdapter.notifyDataSetChanged()
mBinding.numTv.text = "(${items.size}/${mAdapter.maxChooseSize})"
}
} else if (requestCode == PREVIEW_VIDEO) {
requireActivity().setResult(Activity.RESULT_OK, data)
requireActivity().finish()
}
}
override fun onDestroy() {
super.onDestroy()
mAlbumMediaCollection?.onDestroy()
}
companion object {
const val PREVIEW_VIDEO = 100
const val PREVIEW_IMAGE = 101
}
}

View File

@ -3,12 +3,15 @@ package com.gh.gamecenter.qa.editor
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.alibaba.android.arouter.facade.annotation.Route
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.RouteConsts
import com.zhihu.matisse.internal.entity.Item
@Route(path = RouteConsts.activity.previewVideoActivity)
class PreviewVideoActivity : BaseActivity() {
override fun getLayoutId(): Int {
return R.layout.activity_amway

View File

@ -22,7 +22,7 @@ import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.FragmentPreviewVideoBinding
import com.gh.gamecenter.databinding.ItemVideoSelectorBinding
import com.gh.gamecenter.entity.LocalVideoEntity
import com.gh.gamecenter.common.entity.LocalVideoEntity
import com.gh.gamecenter.video.poster.PosterEditActivity
import com.gh.gamecenter.video.upload.view.UploadVideoActivity
import com.lightgame.adapter.BaseRecyclerAdapter

View File

@ -24,8 +24,10 @@ import com.gh.gamecenter.common.base.fragment.ToolbarFragment
import com.gh.gamecenter.common.base.fragment.WaitingDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.entity.LocalVideoEntity
import com.gh.gamecenter.common.entity.NotificationUgc
import com.gh.gamecenter.common.mvvm.Status
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.runOnUiThread
@ -34,6 +36,7 @@ import com.gh.gamecenter.databinding.FragmentVideoPublishBinding
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.qa.BbsType
import com.gh.gamecenter.qa.dialog.ChooseActivityDialogFragment
@ -41,7 +44,6 @@ import com.gh.gamecenter.qa.dialog.ChooseForumActivity
import com.gh.gamecenter.qa.dialog.ChooseSectionDialogFragment
import com.gh.gamecenter.qa.dialog.InputUrlDialogFragment
import com.gh.gamecenter.qa.editor.GameActivity
import com.gh.gamecenter.qa.editor.LocalMediaActivity
import com.gh.gamecenter.video.poster.PosterEditActivity
import com.gh.gamecenter.video.upload.OnUploadListener
import com.gh.gamecenter.video.upload.UploadManager
@ -117,7 +119,7 @@ class VideoPublishFragment : ToolbarFragment(), KeyboardHeightObserver {
startActivityForResult(
LocalMediaActivity.getIntent(
requireContext(),
LocalMediaActivity.ChooseType.VIDEO,
ChooseType.VIDEO,
1,
"发视频帖"
), BaseRichEditorActivity.INSERT_MEDIA_VIDEO_CODE
@ -285,7 +287,7 @@ class VideoPublishFragment : ToolbarFragment(), KeyboardHeightObserver {
startActivityForResult(
LocalMediaActivity.getIntent(
requireContext(),
LocalMediaActivity.ChooseType.VIDEO,
ChooseType.VIDEO,
1,
"发视频帖"
), BaseRichEditorActivity.INSERT_MEDIA_VIDEO_CODE

View File

@ -1,6 +1,5 @@
package com.halo.assistant.fragment.user
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@ -13,7 +12,6 @@ import com.gh.gamecenter.common.base.fragment.WaitingDialogFragment
import com.gh.common.util.*
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.core.utils.EmptyCallback
import com.gh.gamecenter.common.utils.PermissionHelper
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.common.utils.enlargeTouchArea
@ -22,6 +20,8 @@ import com.gh.gamecenter.databinding.FragmentManuallyRealNameBinding
import com.gh.gamecenter.login.entity.IdCardEntity
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
import com.gh.gamecenter.common.utils.UploadImageUtils
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.squareup.picasso.MemoryPolicy
import io.reactivex.disposables.Disposable
@ -150,22 +150,13 @@ class ManuallyRealNameFragment : ToolbarFragment() {
}
private fun selectImage() {
try {
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
startActivityForResult(intent, REQUEST_IMAGE)
} catch (e: ActivityNotFoundException) {
// https://stackoverflow.com/questions/45707678
val getIntent = Intent(Intent.ACTION_GET_CONTENT)
getIntent.type = "image/*"
val pickIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
pickIntent.type = "image/*"
val chooserIntent = Intent.createChooser(getIntent, "Select Image")
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(pickIntent))
startActivityForResult(chooserIntent, REQUEST_IMAGE)
}
val intent = LocalMediaActivity.getIntent(
requireContext(),
ChooseType.IMAGE,
1,
"人工审核"
)
startActivityForResult(intent, REQUEST_IMAGE)
}
private fun removeUploadedImage() {

View File

@ -4,20 +4,23 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.os.Message;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import com.gh.gamecenter.common.base.fragment.WaitingDialogFragment;
import com.gh.gamecenter.common.constant.EntranceConsts;
import com.gh.gamecenter.common.utils.BitmapUtils;
import com.gh.gamecenter.common.utils.UploadImageUtils;
import com.gh.gamecenter.CropImageActivity;
import com.gh.gamecenter.R;
import com.gh.gamecenter.common.retrofit.Response;
import com.gh.gamecenter.common.view.CropMaskFrameView;
import com.gh.gamecenter.common.view.CropMaskView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -65,119 +68,129 @@ public class UserPortraitCropImageActivity extends CropImageActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sp = PreferenceManager.getDefaultSharedPreferences(this);
// CropMaskView cropMaskView = findViewById(R.id.cropMaskView);
// CropMaskFrameView cropMaskFrameView = findViewById(R.id.cropMaskFrameView);
// cropMaskView.updateMaskColor(false);
// cropMaskView.updateMaskShape(true);
// cropMaskFrameView.updateMaskShape(true);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.layout_menu_positive) {
final WaitingDialogFragment postDialog = WaitingDialogFragment.newInstance(getString(R.string.post_img));
postDialog.show(getSupportFragmentManager(), null);
final String path = getCacheDir() + File.separator + System.currentTimeMillis() + ".jpg";
Observable.create((ObservableOnSubscribe<String>) emitter -> {
boolean isSuccess = savePicture(path);
if (isSuccess) {
UploadImageUtils.INSTANCE.uploadImage(UploadImageUtils.UploadType.icon, path, new UploadImageUtils.OnUploadImageListener() {
@Override
public void onSuccess(@NotNull String imageUrl) {
emitter.onNext(imageUrl);
emitter.onComplete();
}
@Override
public void onError(@Nullable Throwable e) {
if (e != null) {
emitter.onError(e);
} else {
emitter.onError(new IllegalStateException("upload image error"));
}
}
@Override
public void onProgress(long total, long progress) {
int percent = (int) (100 * (progress / (float) total));
if (percent >= 100) percent = 99;
if (postDialog != null) {
postDialog.uploadWaitingHint("图片上传中 " + percent + "%");
}
}
});
}
}).subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(new Response<String>() {
@Override
public void onResponse(String url) {
try {
if (postDialog != null) postDialog.dismissAllowingStateLoss();
mBaseHandler.sendEmptyMessage(0);
String iconCount = sp.getString("updateIconCount", null);
long l = System.currentTimeMillis();
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
String time = format.format(new Date(l));
JSONObject jsonObject = new JSONObject();
jsonObject.put("time", time);
if (TextUtils.isEmpty(iconCount)) {
jsonObject.put("count", 1);
} else {
JSONObject json = new JSONObject(iconCount);
String lastTime = json.getString("time");
if (lastTime.equals(time)) {
jsonObject.put("count", json.getInt("count") + 1);
} else {
jsonObject.put("count", 1);
}
}
sp.edit().putString("updateIconCount", jsonObject.toString()).apply();
Intent data = new Intent();
data.putExtra(EntranceConsts.KEY_URL, url);
setResult(RESULT_OK, data);
finish();
} catch (Exception e) {
e.printStackTrace();
public void submitResultAndFinish() {
final WaitingDialogFragment postDialog = WaitingDialogFragment.newInstance(getString(R.string.post_img));
postDialog.show(getSupportFragmentManager(), null);
final String path = getCacheDir() + File.separator + System.currentTimeMillis() + ".jpg";
Observable.create((ObservableOnSubscribe<String>) emitter -> {
boolean isSuccess = cropBitmapAndSave(path);
if (isSuccess) {
UploadImageUtils.INSTANCE.uploadImage(UploadImageUtils.UploadType.icon, path, new UploadImageUtils.OnUploadImageListener() {
@Override
public void onSuccess(@NotNull String imageUrl) {
emitter.onNext(imageUrl);
emitter.onComplete();
}
}
@Override
public void onFailure(HttpException e) {
@Override
public void onError(@Nullable Throwable e) {
if (e != null) {
emitter.onError(e);
} else {
emitter.onError(new IllegalStateException("upload image error"));
}
}
@Override
public void onProgress(long total, long progress) {
int percent = (int) (100 * (progress / (float) total));
if (percent >= 100) percent = 99;
if (postDialog != null) {
postDialog.uploadWaitingHint("图片上传中 " + percent + "%");
}
}
});
}
}).subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(new Response<String>() {
@Override
public void onResponse(String url) {
try {
if (postDialog != null) postDialog.dismissAllowingStateLoss();
try {
if (e != null && e.code() == HttpURLConnection.HTTP_FORBIDDEN && e.response().errorBody() != null) {
JSONObject object = new JSONObject(e.response().errorBody().string());
String detail = object.getString("detail");
if ("too frequent".equals(detail)) {
mBaseHandler.sendEmptyMessage(2);
} else if ("INVALID PICTURE".equals(detail)) {
mBaseHandler.sendEmptyMessage(3);
} else {
mBaseHandler.sendEmptyMessage(1);
}
mBaseHandler.sendEmptyMessage(0);
String iconCount = sp.getString("updateIconCount", null);
long l = System.currentTimeMillis();
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
String time = format.format(new Date(l));
JSONObject jsonObject = new JSONObject();
jsonObject.put("time", time);
if (TextUtils.isEmpty(iconCount)) {
jsonObject.put("count", 1);
} else {
JSONObject json = new JSONObject(iconCount);
String lastTime = json.getString("time");
if (lastTime.equals(time)) {
jsonObject.put("count", json.getInt("count") + 1);
} else {
jsonObject.put("count", 1);
}
}
sp.edit().putString("updateIconCount", jsonObject.toString()).apply();
Intent data = new Intent();
data.putExtra(EntranceConsts.KEY_URL, url);
setResult(RESULT_OK, data);
finish();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(HttpException e) {
if (postDialog != null) postDialog.dismissAllowingStateLoss();
try {
if (e != null && e.code() == HttpURLConnection.HTTP_FORBIDDEN && e.response().errorBody() != null) {
JSONObject object = new JSONObject(e.response().errorBody().string());
String detail = object.getString("detail");
if ("too frequent".equals(detail)) {
mBaseHandler.sendEmptyMessage(2);
} else if ("INVALID PICTURE".equals(detail)) {
mBaseHandler.sendEmptyMessage(3);
} else {
mBaseHandler.sendEmptyMessage(1);
}
} catch (Exception e1) {
e1.printStackTrace();
} else {
mBaseHandler.sendEmptyMessage(1);
}
} catch (Exception e1) {
e1.printStackTrace();
mBaseHandler.sendEmptyMessage(1);
}
});
return false;
}
return super.onMenuItemClick(item);
}
});
}
// 用户头像压缩规则
public boolean savePicture(String path) {
Bitmap clip = mCropImageCustom.clip();
/**
* 裁剪图片并保存
* @param path 目标路径
* @return 是否成功
*/
public boolean cropBitmapAndSave(String path) {
Bitmap background = BitmapUtils.getViewBitmap(findViewById(R.id.iv));
Bitmap cropMask = BitmapUtils.getViewBitmap(findViewById(R.id.cropMaskView));
Bitmap combinedBitmap = BitmapUtils.combineBitmap(background, cropMask, PorterDuff.Mode.XOR);
Bitmap cropedBitmap = BitmapUtils.cropOpaqueArea(combinedBitmap);
try {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(path)));
clip.compress(Bitmap.CompressFormat.WEBP, 100, bos);
cropedBitmap.compress(Bitmap.CompressFormat.WEBP, 100, bos);
bos.flush();
bos.close();
} catch (Exception e) {

View File

@ -2,10 +2,8 @@ package com.halo.assistant.fragment.user.avatar
import android.app.Activity
import android.app.Dialog
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.provider.MediaStore
import android.text.TextUtils
import android.view.Gravity
import android.view.LayoutInflater
@ -18,15 +16,18 @@ import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.common.utils.PermissionHelper.checkStoragePermissionBeforeAction
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.DialogChangeAvatarBinding
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.login.user.UserViewModel
import com.halo.assistant.HaloApp
import com.halo.assistant.fragment.user.UserPortraitCropImageActivity
import com.lightgame.utils.Utils
import com.zhihu.matisse.Matisse
import com.zhihu.matisse.internal.utils.PathUtils
import org.json.JSONException
import org.json.JSONObject
@ -93,8 +94,8 @@ class ChangeAvatarDialog : BaseDialogFragment() {
if (resultCode == Activity.RESULT_OK && data != null) {
when (requestCode) {
REQUEST_MEDIA_ICON -> {
val selectedImage = data.data ?: return
val picturePath = PathUtils.getPath(requireContext(), selectedImage)
val selectedPaths = Matisse.obtainResult(data) ?: return
val picturePath = PathUtils.getPath(requireContext(), selectedPaths[0])
Utils.log("picturePath = $picturePath")
// 上传头像
val intent = UserPortraitCropImageActivity.getIntent(
@ -144,12 +145,9 @@ class ChangeAvatarDialog : BaseDialogFragment() {
e.printStackTrace()
}
}
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
try {
startActivityForResult(intent, REQUEST_MEDIA_ICON)
} catch (e: ActivityNotFoundException) {
Utils.toast(requireContext(), "找不到图片选择器")
}
val intent = LocalMediaActivity.getIntent(requireContext(), ChooseType.IMAGE, 1, "头像选择")
startActivityForResult(intent, REQUEST_MEDIA_ICON)
}
override fun onDarkModeChanged() {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 B

View File

@ -13,27 +13,26 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true" />
<com.gh.gamecenter.common.view.StatusBarView
android:id="@+id/status_bar"
<com.github.piasy.biv.view.BigImageView
android:id="@+id/iv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_height="match_parent" />
<LinearLayout
<com.gh.gamecenter.common.view.CropMaskView
android:id="@+id/cropMaskView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/status_bar"
android:orientation="vertical">
android:layout_height="match_parent" />
<include layout="@layout/reuse_toolbar" />
<com.gh.gamecenter.common.view.CropMaskFrameView
android:id="@+id/cropMaskFrameView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.gh.gamecenter.common.view.CropImageCustom
android:id="@+id/cropimage_custom"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black" />
</LinearLayout>
<include
android:id="@+id/pieceMediaControl"
layout="@layout/piece_media_control"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_alignParentBottom="true" />
</com.gh.gamecenter.common.view.MaterializedRelativeLayout>

View File

@ -1,96 +0,0 @@
<?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="match_parent"
android:background="@color/ui_surface"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/list_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_rv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="52dp">
<TextView
android:id="@+id/previewTv"
android:layout_width="56dp"
android:layout_height="28dp"
android:layout_centerVertical="true"
android:layout_marginLeft="16dp"
android:background="@drawable/border_round_stroke_0dot5_eee_999"
android:enabled="false"
android:gravity="center"
android:text="预览"
android:textColor="@color/text_instance"
android:textSize="12sp" />
<TextView
android:id="@+id/confirmTv"
android:layout_width="56dp"
android:layout_height="28dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="16dp"
android:alpha="0.6"
android:background="@drawable/button_blue_oval"
android:enabled="false"
android:gravity="center"
android:text="确定"
android:textColor="@color/white"
android:textSize="12sp" />
<TextView
android:id="@+id/numTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="12dp"
android:layout_toLeftOf="@+id/confirmTv"
android:text="(0/3)"
android:textColor="@color/text_tertiary" />
</RelativeLayout>
</LinearLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include
android:id="@+id/reuse_ll_loading"
layout="@layout/reuse_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<include
android:id="@+id/reuse_no_connection"
layout="@layout/reuse_no_connection" />
<include
android:id="@+id/reuse_none_data"
layout="@layout/reuse_none_data" />
<include
android:id="@+id/reuse_data_exception"
layout="@layout/reuse_data_exception" />
</RelativeLayout>
</LinearLayout>

View File

@ -132,11 +132,6 @@
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
</style>
<style name="PosterListPopupWindow" parent="@style/Widget.AppCompat.ListPopupWindow">
<item name="android:windowElevation">0dp</item>
<item name="android:popupBackground">@color/text_28282E</item>
</style>
<style name="game_gallery_icon">
<item name="android:layout_width">84dp</item>
<item name="android:layout_height">84dp</item>

View File

@ -123,11 +123,6 @@
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
</style>
<style name="PosterListPopupWindow" parent="@style/Widget.AppCompat.ListPopupWindow">
<item name="android:windowElevation">0dp</item>
<item name="android:popupBackground">@color/text_28282E</item>
</style>
<style name="game_gallery_icon">
<item name="android:layout_width">84dp</item>
<item name="android:layout_height">84dp</item>

View File

@ -29,6 +29,7 @@ import com.gh.gamecenter.common.utils.NotificationHelper.showNotificationHintDia
import com.gh.gamecenter.common.view.BugFixedPopupWindow
import com.gh.gamecenter.common.view.choosepic.ChoosePicAdapter
import com.gh.gamecenter.core.AppExecutor.uiExecutor
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.feedback.R
import com.gh.gamecenter.feedback.databinding.FragmentSuggestAppBinding
import com.gh.gamecenter.feedback.databinding.LayoutOptionPopupBinding
@ -83,11 +84,12 @@ open class SuggestAppFragment : ToolbarFragment() {
val picList = data as List<String>
if (position == mPicAdapter!!.itemCount - 1 && picList.size < 5) {
PermissionHelper.checkStoragePermissionBeforeAction(requireContext()) {
val intent = Intent(
Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val intent = LocalMediaActivity.getIntent(
requireContext(),
LocalMediaActivity.ChooseType.IMAGE,
1,
"意见反馈-${getSuggestType()}"
)
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
startActivityForResult(
intent,
MEDIA_STORE_REQUEST
@ -101,8 +103,12 @@ open class SuggestAppFragment : ToolbarFragment() {
val picList = data as List<*>
if (position == mPicAdapter!!.itemCount - 1 && picList.size < 5) {
PermissionHelper.checkStoragePermissionBeforeAction(requireContext()) {
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
val intent = LocalMediaActivity.getIntent(
requireContext(),
LocalMediaActivity.ChooseType.IMAGE,
1,
"意见反馈-${getSuggestType()}"
)
startActivityForResult(intent, MEDIA_STORE_REQUEST)
}
}

View File

@ -1,7 +1,5 @@
package com.gh.gamecenter.feedback.view.suggest
import android.content.Intent
import android.provider.MediaStore
import android.text.Html
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
@ -12,6 +10,7 @@ import com.gh.gamecenter.common.callback.OnListClickListener
import com.gh.gamecenter.common.entity.SuggestType
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.choosepic.ChoosePicAdapter
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.feedback.R
import com.gh.gamecenter.feedback.databinding.FragmentSuggestCopyrightBinding
import com.gh.gamecenter.feedback.entity.ContactType
@ -67,8 +66,12 @@ class SuggestCopyrightFragment : SuggestAppFragment() {
mCredentialsPicAdapter = ChoosePicAdapter(requireContext(), object : OnListClickListener {
override fun <T : Any?> onListClick(view: View?, position: Int, data: T) {
PermissionHelper.checkStoragePermissionBeforeAction(requireContext()) {
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
val intent = LocalMediaActivity.getIntent(
requireContext(),
LocalMediaActivity.ChooseType.IMAGE,
1,
"意见反馈-${getSuggestType()}"
)
startActivityForResult(intent, MEDIA_STORE_CREDENTIALS_REQUEST)
}
}
@ -87,8 +90,12 @@ class SuggestCopyrightFragment : SuggestAppFragment() {
mAppScreenshotsAdapter = ChoosePicAdapter(requireContext(), object : OnListClickListener {
override fun <T : Any?> onListClick(view: View?, position: Int, data: T) {
PermissionHelper.checkStoragePermissionBeforeAction(requireContext()) {
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
val intent = LocalMediaActivity.getIntent(
requireContext(),
LocalMediaActivity.ChooseType.IMAGE,
1,
"意见反馈-版权申诉"
)
startActivityForResult(intent, MEDIA_STORE_SCREENSHOT_REQUEST)
}
}

View File

@ -47,6 +47,9 @@ public class Item implements Parcelable {
public final long size;
public final long duration; // only for video, in ms
public int positionInAlbum = -1;
public String albumId = "";
private Item(long id, String mimeType, long size, long duration) {
this.id = id;
this.mimeType = mimeType;

View File

@ -20,6 +20,7 @@ import android.net.Uri;
import android.os.Bundle;
import com.zhihu.matisse.R;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.IncapableCause;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.entity.SelectionSpec;
@ -36,6 +37,8 @@ import java.util.Set;
public class SelectedItemCollection {
public static final String STATE_SELECTION = "state_selection";
public static final String STATE_ALBUM = "state_album";
public static final String STATE_DEFAULT_POSITION = "state_default_position";
public static final String STATE_COLLECTION_TYPE = "state_collection_type";
/**
* Empty collection
@ -55,6 +58,8 @@ public class SelectedItemCollection {
public static final int COLLECTION_MIXED = COLLECTION_IMAGE | COLLECTION_VIDEO;
private final Context mContext;
private Set<Item> mItems;
private Album mAlbum;
private int mDefaultPosition = 0;
private int mCollectionType = COLLECTION_UNDEFINED;
public SelectedItemCollection(Context context) {
@ -66,6 +71,8 @@ public class SelectedItemCollection {
mItems = new LinkedHashSet<>();
} else {
List<Item> saved = bundle.getParcelableArrayList(STATE_SELECTION);
mAlbum = bundle.getParcelable(STATE_ALBUM);
mDefaultPosition = bundle.getInt(STATE_DEFAULT_POSITION);
mItems = new LinkedHashSet<>(saved);
mCollectionType = bundle.getInt(STATE_COLLECTION_TYPE, COLLECTION_UNDEFINED);
}
@ -77,12 +84,16 @@ public class SelectedItemCollection {
public void onSaveInstanceState(Bundle outState) {
outState.putParcelableArrayList(STATE_SELECTION, new ArrayList<>(mItems));
outState.putParcelable(STATE_ALBUM, mAlbum);
outState.putInt(STATE_DEFAULT_POSITION, mDefaultPosition);
outState.putInt(STATE_COLLECTION_TYPE, mCollectionType);
}
public Bundle getDataWithBundle() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(STATE_SELECTION, new ArrayList<>(mItems));
bundle.putParcelable(STATE_ALBUM, mAlbum);
bundle.putInt(STATE_DEFAULT_POSITION, mDefaultPosition);
bundle.putInt(STATE_COLLECTION_TYPE, mCollectionType);
return bundle;
}

View File

@ -270,6 +270,7 @@ public class EntranceConsts {
public static final String KEY_VIDEO_LIST = "video_list";
public static final String KEY_CHOOSE_FORUM_TYPE = "choose_forum_type";
public static final String KEY_CHOOSE_MAX_COUNT = "choose_max_count";
public static final String KEY_QUICK_CHOOSE = "quick_choose";
public static final String KEY_COMMENT_COUNT = "comment_count";
public static final String KEY_IS_COMMENT_CONVERSATION = "is_comment_conversation";
public static final String KEY_PARENT_TAG = "parent_tag";

View File

@ -28,6 +28,8 @@ object RouteConsts {
const val suggestionActivity = "/help/SuggestionActivity"
const val messageWrapperActivity = "/message/messageWrapperActivity"
const val previewVideoActivity = "/video/previewVideoActivity"
}
object fragment {

View File

@ -1,4 +1,4 @@
package com.gh.gamecenter.entity
package com.gh.gamecenter.common.entity
import android.net.Uri
import android.os.Parcelable

View File

@ -0,0 +1,38 @@
package com.gh.gamecenter.common.structure
import android.os.Parcel
import android.os.Parcelable
class ParcelableMap<K, V>(
val map: HashMap<K, V> = HashMap()
) : Parcelable {
constructor(parcel: Parcel) : this() {
val size = parcel.readInt()
for (i in 0 until size) {
val key = parcel.readValue(ParcelableMap::class.java.classLoader) as K
val value = parcel.readValue(ParcelableMap::class.java.classLoader) as V
map[key] = value
}
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(map.size)
for (entry in map.entries) {
parcel.writeValue(entry.key)
parcel.writeValue(entry.value)
}
}
override fun describeContents(): Int = 0
companion object CREATOR : Parcelable.Creator<ParcelableMap<*, *>> {
override fun createFromParcel(parcel: Parcel): ParcelableMap<*, *> {
return ParcelableMap<Any, Any>(parcel)
}
override fun newArray(size: Int): Array<ParcelableMap<*, *>?> {
return arrayOfNulls(size)
}
}
}

View File

@ -5,13 +5,19 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.view.View;
import androidx.annotation.IntRange;
import androidx.annotation.RequiresApi;
@ -600,4 +606,73 @@ public class BitmapUtils {
context.sendBroadcast(intent);
Utils.log("保存分享图片路径:" + imageFile.getAbsolutePath());
}
/**
* 对两个 bitmap 进行 PorterDuff 操作,并返回一个新的 bitmap
* @param mode 你想要操作的模式
* @return PorterDuff 处理后的 bitmap
*/
public static Bitmap combineBitmap(Bitmap sourceBitmap, Bitmap destBitmap, PorterDuff.Mode mode) {
int width = Math.max(sourceBitmap.getWidth(), destBitmap.getWidth());
int height = Math.max(sourceBitmap.getHeight(), destBitmap.getHeight());
Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(resultBitmap);
// Draw sourceBitmap first
canvas.drawBitmap(sourceBitmap, 0f, 0f, null);
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(mode));
// Draw destBitmap on top of sourceBitmap with porterDuff mode
canvas.drawBitmap(destBitmap, 0f, 0f, paint);
return resultBitmap;
}
/**
* 移除 bitmap 中的透明部分,返回一个裁剪后的 bitmap
*/
public static Bitmap cropOpaqueArea(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int minX = width, minY = height, maxX = -1, maxY = -1;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixel = bitmap.getPixel(x, y);
if (Color.alpha(pixel) == 255) { // Pixel is not opaque
if (x < minX) minX = x;
if (y < minY) minY = y;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
} else {
// make all opaque pixels transparent
bitmap.setPixel(x, y, Color.TRANSPARENT);
}
}
}
if (maxX < minX || maxY < minY) {
// No non-transparent pixels found, return a blank Bitmap
return Bitmap.createBitmap(1, 1, bitmap.getConfig());
}
// Crop the bitmap
Rect cropRect = new Rect(minX, minY, maxX + 1, maxY + 1);
return Bitmap.createBitmap(bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height());
}
/**
* 获取 View 的 Bitmap
*/
public static Bitmap getViewBitmap(View view) {
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
}

View File

@ -0,0 +1,9 @@
package com.gh.gamecenter.common.utils
class FIFOMap<K, V>(private val maxEntries: Int) : LinkedHashMap<K, V>(maxEntries + 1, 1.0f, false) {
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?): Boolean {
return size > maxEntries
}
}

View File

@ -491,11 +491,11 @@ object ImageUtils {
fun displayResizeMedia(
draweeView: SimpleDraweeView,
url: String,
uri: Uri,
resizeWidthDp: Int,
resizeHeightDp: Int
) {
val request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
val request = ImageRequestBuilder.newBuilderWithSource(uri)
.setResizeOptions(ResizeOptions(resizeWidthDp, resizeHeightDp))
.build()
val controller = Fresco.newDraweeControllerBuilder()

View File

@ -0,0 +1,60 @@
package com.gh.gamecenter.common.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.view.CropMaskView.Companion.MASK_SHAPE_CIRCLE
import com.gh.gamecenter.common.view.CropMaskView.Companion.MASK_SHAPE_SQUARE
import com.gh.gamecenter.common.view.CropMaskView.Companion.shapeHorizontalPadding
import kotlin.math.min
class CropMaskFrameView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
View(context, attrs) {
private var paint: Paint? = null
private var maskShape = MASK_SHAPE_CIRCLE
init {
paint = Paint()
paint?.isAntiAlias = true
}
fun updateMaskShape(useSquare: Boolean) {
maskShape = if (useSquare) MASK_SHAPE_SQUARE else MASK_SHAPE_CIRCLE
invalidate()
}
protected override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
var saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG)
paint?.strokeWidth = 1F.dip2px().toFloat()
paint?.color = Color.WHITE
paint?.style = Paint.Style.STROKE
// Draw the circular area with CLEAR mode to make it transparent
if (maskShape == MASK_SHAPE_CIRCLE) {
var radius = (min(width.toDouble(), height.toDouble()) / 2f).toFloat() - shapeHorizontalPadding
canvas.drawCircle(width / 2f, height / 2f, radius, paint!!)
} else {
val maxSquareSize = min(width, height).toFloat()
val startX = shapeHorizontalPadding.toFloat()
val startY = (height - maxSquareSize) / 2
canvas.drawRect(
startX,
startY,
startX + maxSquareSize - 2 * shapeHorizontalPadding,
startY + maxSquareSize,
paint!!)
}
canvas.restoreToCount(saved)
}
}

View File

@ -0,0 +1,80 @@
package com.gh.gamecenter.common.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.util.AttributeSet
import android.view.View
import com.gh.gamecenter.common.utils.dip2px
import kotlin.math.min
class CropMaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
View(context, attrs) {
private var paint: Paint? = null
private var maskColor: Int = defaultMaskColor
private var maskShape = MASK_SHAPE_CIRCLE
private val xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
init {
paint = Paint()
paint?.isAntiAlias = true
}
fun updateMaskColor(useDefault: Boolean) {
maskColor = if (useDefault) defaultMaskColor else darkerMaskColor
invalidate()
}
fun updateMaskShape(useSquare: Boolean) {
maskShape = if (useSquare) MASK_SHAPE_SQUARE else MASK_SHAPE_CIRCLE
invalidate()
}
protected override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
var saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG)
// Draw the 50% transparent dark background
canvas.drawColor(maskColor)
// Set the mode to clear to punch out the circle
paint?.xfermode = xfermode
// Draw the circular area with CLEAR mode to make it transparent
if (maskShape == MASK_SHAPE_CIRCLE) {
var radius = (min(width.toDouble(), height.toDouble()) / 2f).toFloat() - shapeHorizontalPadding
canvas.drawCircle(width / 2f, height / 2f, radius, paint!!)
} else {
val maxSquareSize = min(width, height).toFloat()
val startX = shapeHorizontalPadding.toFloat()
val startY = (height - maxSquareSize) / 2
canvas.drawRect(
startX,
startY,
startX + maxSquareSize - 2 * shapeHorizontalPadding,
startY + maxSquareSize,
paint!!)
}
// Reset the Xfermode to null after drawing the transparent shape
paint?.xfermode = null
canvas.restoreToCount(saved)
}
companion object {
private val defaultMaskColor: Int = Color.argb(0x80, 0, 0, 0)
private val darkerMaskColor: Int = Color.argb(0xCC, 0, 0, 0)
internal val shapeHorizontalPadding = 16F.dip2px()
internal const val MASK_SHAPE_SQUARE = 0
internal const val MASK_SHAPE_CIRCLE = 1
}
}

View File

@ -90,6 +90,8 @@ class DraggableBigImageView @JvmOverloads constructor(
mListener?.onRelease(this, scaleX)
} else {
val offsetY = translationY
if (offsetY == 0F) return
val fraction = min(1F, offsetY / height)
mListener?.onRestore(this, fraction)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="999dp" />
<solid android:color="@color/black_alpha_50" />
</shape>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:fillColor="#2496FF"
android:fillType="evenOdd"
android:pathData="M15.905,4.345C16.198,4.638 16.198,5.112 15.905,5.405L7.28,14.03C6.987,14.323 6.513,14.323 6.22,14.03L2.095,9.905C1.802,9.612 1.802,9.138 2.095,8.845C2.388,8.552 2.862,8.552 3.155,8.845L6.75,12.439L14.845,4.345C15.138,4.052 15.612,4.052 15.905,4.345Z"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path
android:pathData="M0,0h24v24h-24z"/>
<path
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:pathData="M4.793,4.793C5.183,4.402 5.817,4.402 6.207,4.793L12,10.586L17.793,4.793C18.183,4.402 18.817,4.402 19.207,4.793C19.598,5.183 19.598,5.817 19.207,6.207L13.414,12L19.207,17.793C19.598,18.183 19.598,18.817 19.207,19.207C18.817,19.598 18.183,19.598 17.793,19.207L12,13.414L6.207,19.207C5.817,19.598 5.183,19.598 4.793,19.207C4.402,18.817 4.402,18.183 4.793,17.793L10.586,12L4.793,6.207C4.402,5.817 4.402,5.183 4.793,4.793Z"/>
</group>
</vector>

View File

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillAlpha="0.1"
android:fillColor="#ffffff"
android:pathData="M8,8m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"/>
<path
android:fillColor="#00000000"
android:pathData="M11,7L8,10L5,7"
android:strokeWidth="1.5"
android:strokeAlpha="0.6"
android:strokeColor="#ffffff"
android:strokeLineCap="round"
android:strokeLineJoin="round"/>
</vector>

View File

@ -1,72 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<com.gh.gamecenter.common.view.MaterializedConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/ui_background_fixed_dark"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/normal_toolbar_container"
android:layout_width="match_parent"
android:layout_height="@dimen/appbar_height">
<androidx.appcompat.widget.Toolbar
android:id="@+id/normal_toolbar"
style="@style/Base_ToolbarStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentInsetEnd="0dp"
app:contentInsetStartWithNavigation="0dp"
app:navigationIcon="@null">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/backContainer"
android:layout_width="48dp"
android:layout_height="48dp">
<ImageView
android:id="@+id/backBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
app:srcCompat="@drawable/ic_bar_back" />
</FrameLayout>
<TextView
android:id="@+id/normal_title"
style="@style/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:textColor="@color/text_primary"
android:textSize="16sp" />
<ImageView
android:id="@+id/arrowIv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="-42dp"
android:layout_toRightOf="@+id/normal_title"
android:src="@drawable/ic_video_arrow_down" />
</RelativeLayout>
</androidx.appcompat.widget.Toolbar>
</RelativeLayout>
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</com.gh.gamecenter.common.view.MaterializedConstraintLayout>

View File

@ -17,10 +17,20 @@
app:roundingBorderColor="@color/black_alpha_10"
app:roundingBorderWidth="0.5dp" />
<ImageView
android:id="@+id/previewMask"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/black_alpha_50"
app:layout_constraintBottom_toBottomOf="@id/preview"
app:layout_constraintEnd_toEndOf="@id/preview"
app:layout_constraintStart_toStartOf="@id/preview"
app:layout_constraintTop_toTopOf="@id/preview" />
<ImageView
android:id="@+id/checkImageView"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp"
android:src="@drawable/ic_choose_media_normal"
@ -33,8 +43,8 @@
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:text="1"
android:textColor="@color/white"
android:textSize="10sp"
android:textColor="@color/text_aw_primary"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/checkImageView"
app:layout_constraintEnd_toEndOf="@+id/checkImageView"
@ -43,13 +53,15 @@
<TextView
android:id="@+id/durationTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="7dp"
android:layout_marginBottom="8dp"
android:layout_width="36dp"
android:layout_height="16dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="4dp"
android:background="@drawable/background_shape_black_alpha_50_radius_999"
android:text="00:00"
android:textColor="@color/white"
android:textSize="12sp"
android:gravity="center"
android:textColor="@color/text_aw_primary"
android:textSize="11sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,15 +1,17 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="64dp"
android:background="@color/ui_surface">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/ui_surface_fixed_dark">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/album_cover"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp" />
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:roundedCornerRadius="4dp" />
<TextView
android:id="@+id/album_name"
@ -22,7 +24,7 @@
android:layout_toRightOf="@id/album_cover"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/text_primary"
android:textColor="@color/text_aw_primary"
android:textSize="14sp" />
<TextView
@ -37,13 +39,23 @@
android:layout_toRightOf="@id/album_cover"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/text_tertiary"
android:textColor="@color/text_aw_tertiary"
android:textSize="12sp" />
<ImageView
android:id="@+id/checkIv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
app:srcCompat="@drawable/ic_check" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_height="1px"
android:layout_alignParentBottom="true"
android:background="@color/ui_divider" />
android:layout_alignStart="@id/album_name"
android:background="@color/ui_divider_fixed_dark" />
</RelativeLayout>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="DialogWindowTransparent" parent="android:style/Theme.Dialog">
<item name="android:background">@android:color/transparent</item>
<item name="android:windowBackground">@android:color/transparent</item>
@ -112,4 +113,9 @@
<item name="android:layout_height">1dp</item>
<item name="android:background">@color/ui_background</item>
</style>
<style name="PosterListPopupWindow" parent="@style/Widget.AppCompat.ListPopupWindow">
<item name="android:windowElevation">0dp</item>
<item name="android:popupBackground">@color/text_28282E</item>
</style>
</resources>

View File

@ -45,16 +45,19 @@
<!-- 页面 -->
<!-- 背景 -->
<color name="ui_background">#F5F5F5</color>
<color name="ui_background_fixed_dark">#1A1A1A</color>
<!-- 表面 -->
<color name="ui_surface">#FFFFFF</color>
<color name="ui_surface_45">#73FFFFFF</color>
<color name="ui_surface_0">#00FFFFFF</color>
<color name="ui_surface_fixed_dark">#232323</color>
<!-- 容器-1 -->
<color name="ui_container_1">#F8F8F8</color>
<!-- 容器-2 -->
<color name="ui_container_2">#F5F5F5</color>
<!-- 分隔线 -->
<color name="ui_divider">#EEEEEE</color>
<color name="ui_divider_fixed_dark">#333333</color>
<!-- 高亮 -->
<color name="ui_highlight">#0A2496FF</color>
<!-- 骨架-框架 -->

View File

@ -1,6 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Transparent" parent="AppCompatTheme.APP">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowActionBarOverlay">true</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowAnimationStyle">@android:style/Animation</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<style name="DialogWindowTransparent" parent="android:style/Theme.Dialog">
<item name="android:background">@android:color/transparent</item>
<item name="android:windowBackground">@android:color/transparent</item>
@ -143,6 +152,11 @@
<item name="android:background">@color/ui_background</item>
</style>
<style name="PosterListPopupWindow" parent="@style/Widget.AppCompat.ListPopupWindow">
<item name="android:windowElevation">0dp</item>
<item name="android:popupBackground">@color/text_28282E</item>
</style>
<!-- 字体 -->
<!-- 标题1 -->
<style name="TextTitle1">

View File

@ -2,4 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gh.gamecenter.feature">
<application>
<activity
android:name=".selector.LocalMediaActivity"
android:screenOrientation="portrait" />
</application>
</manifest>

View File

@ -0,0 +1,55 @@
package com.gh.gamecenter.feature.selector
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.updateStatusBarColor
import com.gh.gamecenter.common.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.core.utils.DisplayUtils
import kotlin.apply
import kotlin.jvm.java
/**
* 选择本地视频/图片
*/
class LocalMediaActivity : BaseActivity() {
private var localMediaFragment: LocalMediaFragment? = null
override fun getLayoutId(): Int = R.layout.activity_video_tablayout_viewpager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
updateStatusBarColor(R.color.ui_surface_fixed_dark, R.color.ui_surface_fixed_dark)
localMediaFragment = LocalMediaFragment().apply { arguments = intent.extras }
supportFragmentManager.beginTransaction()
.replace(R.id.container, localMediaFragment!!, LocalMediaFragment::class.java.name)
.commitAllowingStateLoss()
DisplayUtils.transparentStatusAndNavigation(this)
}
override fun isAutoResetViewBackgroundEnabled(): Boolean = false
companion object {
fun getIntent(context: Context,
chooseType: ChooseType,
maxChooseCount: Int = 1,
entrance: String): Intent {
return Intent(context, LocalMediaActivity::class.java).apply {
putExtra(EntranceConsts.KEY_TYPE, chooseType.value)
putExtra(EntranceConsts.KEY_CHOOSE_MAX_COUNT, maxChooseCount)
putExtra(EntranceConsts.KEY_QUICK_CHOOSE, chooseType == ChooseType.IMAGE && maxChooseCount == 1)
putExtra(EntranceConsts.KEY_ENTRANCE, entrance)
}
}
}
}
enum class ChooseType(val value: String) {
VIDEO("video"),
IMAGE("image")
}

View File

@ -0,0 +1,117 @@
package com.gh.gamecenter.feature.selector
import android.content.Context
import android.database.Cursor
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.R
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.databinding.LocalVideoItemBinding
import com.gh.gamecenter.common.utils.FIFOMap
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.enlargeTouchArea
import com.zhihu.matisse.internal.entity.Item
import com.zhihu.matisse.internal.ui.adapter.RecyclerViewCursorAdapter
class LocalMediaAdapter(
val context: Context,
val chooseType: String,
val isQuickChoose: Boolean,
val entrance: String,
val viewModel: LocalMediaViewModel
) : RecyclerViewCursorAdapter<LocalVideoPreviewViewHolder>(null) {
val fifoMap = FIFOMap<String, View>(21)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocalVideoPreviewViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.local_video_item, parent, false)
return LocalVideoPreviewViewHolder(LocalVideoItemBinding.bind(view))
}
override fun onBindViewHolder(holder: LocalVideoPreviewViewHolder, cursor: Cursor?, position: Int) {
val item = Item.valueOf(cursor).also {
it.positionInAlbum = position
it.albumId = viewModel.getAlbumId()
}
holder.binding.durationTv.goneIf(chooseType == ChooseType.IMAGE.value)
ImageUtils.displayResizeMedia(holder.binding.preview, item.contentUri, 120, 120)
holder.binding.durationTv.text = TimeUtils.formatVideoDuration(item.duration / 1000)
var selectedResource: Int
if (isQuickChoose) {
holder.binding.previewMask.visibility = View.GONE
holder.binding.chooseCountTv.visibility = View.GONE
holder.binding.checkImageView.visibility = View.GONE
}
if (viewModel.isSelected(item)) {
if (viewModel.maxSelectionSize == 1) {
holder.binding.chooseCountTv.visibility = View.GONE
selectedResource = R.drawable.ic_choose_media_selected
} else {
holder.binding.chooseCountTv.visibility = View.VISIBLE
holder.binding.chooseCountTv.text = (viewModel.indexOfSelected(item) + 1).toString()
selectedResource = R.drawable.ic_choose_media_bg
}
holder.binding.previewMask.visibility = View.VISIBLE
} else {
holder.binding.chooseCountTv.visibility = View.GONE
holder.binding.previewMask.visibility = View.GONE
selectedResource = R.drawable.ic_choose_media_normal
}
holder.binding.checkImageView.setImageResource(selectedResource)
holder.binding.checkImageView.enlargeTouchArea(8F.dip2px())
holder.binding.checkImageView.setOnClickListener {
if (viewModel.isSelected(item)) {
viewModel.removeSelection(item)
notifyItemChanged(position)
} else {
if (viewModel.addSelection(item)) {
notifyItemChanged(position)
} else {
if (chooseType == ChooseType.IMAGE.value) {
ToastUtils.showToast("至多选择${viewModel.maxSelectionSize}张图片")
} else {
ToastUtils.showToast("至多选择${viewModel.maxSelectionSize}条视频")
}
}
}
}
if (isQuickChoose) {
holder.binding.previewMask.visibility = View.GONE
holder.binding.chooseCountTv.visibility = View.GONE
holder.binding.checkImageView.visibility = View.GONE
holder.itemView.setOnClickListener {
viewModel.addSelection(item)
viewModel.postSelectedResult()
}
} else {
holder.itemView.setOnClickListener {
viewModel.previewImage(
onlySelected = false,
positionInAlbum = position,
previewItem = item,
urlAndViewMap = fifoMap
)
}
}
fifoMap.put(item.uri.toString(), holder.binding.preview)
}
override fun getItemViewType(position: Int, cursor: Cursor?) = 0
}
class LocalVideoPreviewViewHolder(val binding: LocalVideoItemBinding) : BaseRecyclerViewHolder<Any>(binding.root)

View File

@ -0,0 +1,339 @@
package com.gh.gamecenter.feature.selector
import android.animation.ObjectAnimator
import android.app.Activity
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.AdapterView
import android.widget.PopupWindow
import androidx.recyclerview.widget.GridLayoutManager
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.entity.LocalVideoEntity
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.tryWithDefaultCatch
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.enlargeTouchArea
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.common.view.GridSpacingItemDecoration
import com.gh.gamecenter.feature.databinding.FragmentLocalMediaBinding
import com.zhihu.matisse.Matisse
import com.zhihu.matisse.MimeType
import com.gh.gamecenter.feature.R
import com.zhihu.matisse.internal.entity.Album
import com.zhihu.matisse.internal.entity.SelectionSpec
import com.zhihu.matisse.internal.model.AlbumCollection
import com.zhihu.matisse.internal.model.AlbumMediaCollection
import com.zhihu.matisse.internal.utils.PathUtils
import com.zhihu.matisse.ui.MatisseActivity
import java.util.ArrayList
import kotlin.collections.count
import kotlin.collections.forEach
import kotlin.collections.map
import kotlin.collections.toList
import kotlin.jvm.java
import kotlin.text.split
class LocalMediaFragment : BaseFragment<Any>(), AlbumMediaCollection.AlbumMediaCallbacks {
private lateinit var binding: FragmentLocalMediaBinding
private lateinit var adapter: LocalMediaAdapter
private var albumMediaCollection: AlbumMediaCollection? = null
private var chooseType = ""
private var maxChooseCount = 1
private var isQuickChoose = false
private lateinit var viewModel: LocalMediaViewModel
private lateinit var albumsSpanner: VideoAlbumsSpanner
private lateinit var albumsAdapter: VideoAlbumsAdapter
private val albumCollection = AlbumCollection()
private var isFirstAlbumLoad = true
override fun getLayoutId(): Int = 0
override fun getInflatedLayout(): View {
binding = FragmentLocalMediaBinding.inflate(LayoutInflater.from(requireContext()), null, false)
return binding.root
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
chooseType = arguments?.getString(EntranceConsts.KEY_TYPE) ?: ""
maxChooseCount = savedInstanceState?.getInt(EntranceConsts.KEY_CHOOSE_MAX_COUNT, 1)
?: arguments?.getInt(EntranceConsts.KEY_CHOOSE_MAX_COUNT, 1)
?: 1
isQuickChoose = savedInstanceState?.getBoolean(EntranceConsts.KEY_QUICK_CHOOSE, false)
?: arguments?.getBoolean(EntranceConsts.KEY_QUICK_CHOOSE, false)
?: false
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initAlbumsSpinner()
if (chooseType == ChooseType.VIDEO.value) {
binding.normalTitle.text = "本地视频"
} else {
binding.normalTitle.text = "本地图片"
}
binding.normalTitle.enlargeTouchArea(10F.dip2px())
binding.arrowIv.setOnClickListener { binding.normalTitle.performClick() }
binding.normalTitle.setOnClickListener {
albumsSpanner.show(280F.dip2px())
performArrowRotateAnimation(isClockWise = true)
}
binding.listRv.layoutManager = GridLayoutManager(requireContext(), 3)
binding.listRv.addItemDecoration(GridSpacingItemDecoration(3, 4F.dip2px(), false))
if (isQuickChoose) {
binding.pieceMediaControl.root.visibility = View.GONE
}
viewModel = viewModelProviderFromParent(LocalMediaViewModel.Factory(maxChooseCount, LocalMediaRepository()))
adapter = LocalMediaAdapter(requireContext(), chooseType, isQuickChoose, mEntrance, viewModel)
binding.listRv.adapter = adapter
binding.listRefresh.isEnabled = false
viewModel.selectedMediaListLiveData.observe(viewLifecycleOwner) {
adapter.notifyDataSetChanged()
}
binding.pieceMediaControl.previewTv.setOnClickListener {
if (chooseType == ChooseType.VIDEO.value) {
ARouter.getInstance().build(RouteConsts.activity.previewVideoActivity)
?.withParcelableArrayList(EntranceConsts.KEY_VIDEO_LIST, viewModel.selectedMediaList)
?.navigation(requireActivity(), PREVIEW_VIDEO)
} else {
viewModel.previewImage(onlySelected = true, 0, null, emptyMap())
}
}
binding.pieceMediaControl.confirmTv.setOnClickListener {
viewModel.postSelectedResult()
}
viewModel.resultLiveData.observe(viewLifecycleOwner) {
if (it) {
val intent = Intent()
if (chooseType == ChooseType.VIDEO.value) {
val localVideoList = arrayListOf<LocalVideoEntity>()
viewModel.selectedMediaList.forEach {
val path = PathUtils.getPath(requireContext(), it.contentUri)
if (path == null) {
toast("视频已不存在,请重新选择")
return@forEach
}
val id = MD5Utils.getUrlMD5(path) + System.currentTimeMillis()
val format = getFileFormat(it.mimeType)
localVideoList.add(
LocalVideoEntity(
id,
path,
contentUri = it.contentUri,
duration = it.duration,
format = format,
size = it.size
)
)
}
intent.putExtra(LocalVideoEntity::class.java.name, localVideoList)
} else {
val data = viewModel.selectedMediaList.map { it.contentUri }.toList()
val path = data.map { PathUtils.getPath(requireContext(), it) }.toList()
intent.putParcelableArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION, ArrayList<Uri>(data))
intent.putStringArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION_PATH, ArrayList<String>(path))
}
requireActivity().setResult(Activity.RESULT_OK, intent)
requireActivity().finish()
}
}
viewModel.previewImageLiveData.observe(viewLifecycleOwner) {
val previewFragment = LocalMediaPreviewFragment.getInstance(
selectedItemList = it.previewList,
album = it.album,
positionInAlbum = it.positionInAlbum,
originalUrlAndViewMap = it.urlAndViewMap,
previewItem = it.previewItem
)
viewModel.updateAlbumCursor(adapter.cursor)
parentFragmentManager.beginTransaction()
.replace(R.id.layout_activity_content, previewFragment)
.addToBackStack(null)
.commitAllowingStateLoss()
}
viewModel.selectedCountLiveData.observe(viewLifecycleOwner) {
val isNotEmpty = it > 0
binding.pieceMediaControl.previewTv.isEnabled = isNotEmpty
binding.pieceMediaControl.confirmTv.isEnabled = isNotEmpty
if (isNotEmpty) {
binding.pieceMediaControl.previewTv.setTextColor(R.color.text_instance.toColor(requireContext()))
binding.pieceMediaControl.confirmTv.alpha = 1f
binding.pieceMediaControl.confirmTv.text = "确定(${it})"
} else {
binding.pieceMediaControl.confirmTv.alpha = 0.6f
binding.pieceMediaControl.previewTv.setTextColor(R.color.text_secondary.toColor(requireContext()))
binding.pieceMediaControl.confirmTv.text = "确定"
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(EntranceConsts.KEY_CHOOSE_MAX_COUNT, maxChooseCount)
outState.putBoolean(EntranceConsts.KEY_QUICK_CHOOSE, isQuickChoose)
}
private fun initAlbumsSpinner() {
albumsSpanner = VideoAlbumsSpanner(requireContext())
albumsAdapter = VideoAlbumsAdapter(requireContext())
albumsSpanner.setPopupAnchorView(binding.normalToolbar)
albumsSpanner.setAdapter(albumsAdapter)
albumsSpanner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
albumCollection.setStateCurrentSelection(position)
albumsAdapter.cursor.moveToPosition(position)
albumsAdapter.updateSelectedPosition(position)
val album = Album.valueOf(albumsAdapter.cursor)
if (album.isAll && SelectionSpec.getInstance().capture) {
album.addCaptureCount()
}
binding.normalTitle.text = album.getDisplayName(requireContext())
loadMedia(album)
}
}
albumsSpanner.setDismissListener(PopupWindow.OnDismissListener {
performArrowRotateAnimation(isClockWise = false)
})
// 必须加这行代码,[SelectionSpec]是单例模式下次使用必须先更新MimeType
val mimeType = if (chooseType == ChooseType.VIDEO.value) {
MimeType.ofVideo()
} else {
MimeType.ofImage()
}
val maxChooseCount = arguments?.getInt(EntranceConsts.KEY_CHOOSE_MAX_COUNT, 1) ?: 1
Matisse.from(this).choose(mimeType).showSingleMediaType(true).maxSelectable(maxChooseCount)
albumCollection.onCreate(requireActivity(), object : AlbumCollection.AlbumCallbacks {
override fun onAlbumLoad(cursor: Cursor?) {
if (isFirstAlbumLoad) {
isFirstAlbumLoad = false
albumsAdapter.swapCursor(cursor)
mBaseHandler.post {
cursor?.moveToPosition(albumCollection.currentSelection)
val album = Album.valueOf(cursor)
if (album.isAll && SelectionSpec.getInstance().capture) {
album.addCaptureCount()
}
loadMedia(album)
}
}
}
override fun onAlbumReset() {
// do nothing
}
})
albumCollection.loadAlbums()
}
private fun performArrowRotateAnimation(isClockWise: Boolean) {
val arrowIv: View = binding.arrowIv
val startAngle = if (isClockWise) 0F else 180F
val endAngle = if (isClockWise) 180F else 0F
val clockwiseAnimator = ObjectAnimator.ofFloat(arrowIv, "rotation", startAngle, endAngle)
clockwiseAnimator.duration = 200
clockwiseAnimator.start()
}
private fun getFileFormat(mimeType: String?): String {
var format = ""
tryWithDefaultCatch {
if (mimeType != null) {
val split = mimeType.split("/")
format = if (split.count() >= 2) {
split[1]
} else {
mimeType
}
}
}
return format
}
override fun onAlbumMediaReset() {
adapter.swapCursor(null)
}
override fun onAlbumMediaLoad(cursor: Cursor?) {
adapter.swapCursor(cursor)
binding.reuseNoneData.reuseNoneData.visibility = View.GONE
binding.reuseLlLoading.root.visibility = View.GONE
binding.reuseNoConnection.root.visibility = View.GONE
binding.listRefresh.isRefreshing = false
}
fun loadMedia(album: Album) {
albumMediaCollection?.onDestroy()
albumMediaCollection = AlbumMediaCollection()
albumMediaCollection?.onCreate(requireActivity(), this@LocalMediaFragment)
viewModel.updateAlbum(album)
albumMediaCollection?.load(album)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (data == null) return
if (requestCode == PREVIEW_VIDEO) {
requireActivity().setResult(Activity.RESULT_OK, data)
requireActivity().finish()
}
}
override fun onDestroy() {
super.onDestroy()
albumMediaCollection?.onDestroy()
}
companion object {
const val PREVIEW_VIDEO = 100
const val PREVIEW_IMAGE = 101
}
}

View File

@ -0,0 +1,892 @@
package com.gh.gamecenter.feature.selector
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.graphics.PointF
import android.net.Uri
import android.os.Bundle
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.view.animation.DecelerateInterpolator
import android.widget.ImageView
import androidx.core.view.doOnLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.ChangeBounds
import androidx.transition.ChangeTransform
import androidx.transition.Transition
import androidx.transition.TransitionListenerAdapter
import androidx.transition.TransitionManager
import androidx.transition.TransitionSet
import androidx.transition.TransitionValues
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import androidx.viewpager.widget.ViewPager.OnPageChangeListener
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipeline
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.structure.ParcelableMap
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.enlargeTouchArea
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.common.view.DraggableBigImageView
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.SimpleImageLoader
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.core.utils.doOnEnd
import com.gh.gamecenter.core.utils.doOnStart
import com.gh.gamecenter.feature.R
import com.gh.gamecenter.feature.databinding.FragmentLocalMediaPreviewBinding
import com.github.piasy.biv.view.BigImageView
import com.github.piasy.biv.view.FrescoImageViewFactory
import com.lightgame.listeners.OnBackPressedListener
import com.zhihu.matisse.internal.entity.Album
import com.zhihu.matisse.internal.entity.Item
import java.io.File
import kotlin.collections.set
import kotlin.math.abs
import kotlin.math.roundToInt
class LocalMediaPreviewFragment : BaseFragment<Any>(), OnPageChangeListener, OnBackPressedListener {
private var animating = false
private var imageRatio = 1F
private var viewRatio = 1F
lateinit var viewPager: ViewPager
lateinit var bottomContainer: View
lateinit var toolbarContainer: View
lateinit var backgroundView: View
private val binding by lazy { FragmentLocalMediaPreviewBinding.inflate(layoutInflater) }
private var viewPagerAdapter: ViewImageAdapter? = null
private var parentViewModel: LocalMediaViewModel? = null
private var viewModel: LocalMediaPreviewViewModel? = null
private var imagePipeline: ImagePipeline? = null
private var quickSelectAdapter: QuickSelectAdapter? = null
private var bigImageView: BigImageView? = null
private var container: ViewGroup? = null
private var album: Album? = null
private var previewItem: Item? = null
private var itemList: ArrayList<Item>? = null
private var containerMap = SparseArray<ViewGroup>()
private var positionInAlbum = 0
private var useEnterAndExitAnimation = false
private var originLeftMap: HashMap<String, Int>? = null
private var originTopMap: HashMap<String, Int>? = null
private var originHeightMap: HashMap<String, Int>? = null
private var originWidthMap: HashMap<String, Int>? = null
private var originLeft = 0
private var originTop = 0
private var originHeight = 0
private var originWidth = 0
private var originCenterX = 0
private var originCenterY = 0
private var originHeightWidthRatio = 0F
private var targetHeight = 0F
private var targetWidth = 0F
private var scaleX = 0F
private var scaleY = 0F
private var translationX = 0F
private var translationY = 0F
private var targetCenterX = 0F
private var targetCenterY = 0F
private var currentDisplayingItem: Item? = null
private var quickSelectRecyclerView: RecyclerView? = null
override fun getLayoutId() = R.layout.fragment_local_media_preview
override fun getInflatedLayout(): View? = binding.root
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
parentViewModel = viewModelProviderFromParent()
viewModel = viewModelProvider(LocalMediaPreviewViewModel.Factory(parentViewModel!!.localMediaRepo))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewPager = binding.imageDetailPage
bottomContainer = binding.bottomContainer
backgroundView = binding.backgroundView
toolbarContainer = binding.toolbarContainer
quickSelectRecyclerView = binding.quickSelectRecyclerView
imagePipeline = Fresco.getImagePipeline()
positionInAlbum = savedInstanceState?.getInt(EntranceConsts.KEY_CURRENTITEM, 0) ?: 0
arguments?.let {
itemList = it.getParcelableArrayList(KEY_ITEM_LIST) ?: arrayListOf()
album = it.getParcelable(KEY_ALBUM)
previewItem = it.getParcelable(KEY_PREVIEW_ITEM)
positionInAlbum = it.getInt(KEY_POSITION_IN_ALBUM, 0)
useEnterAndExitAnimation = it.getBoolean(KEY_USE_ENTER_AND_EXIT_ANIMATION)
originLeftMap = it.getParcelable<ParcelableMap<String, Int>>(KEY_LEFT)?.map
originTopMap = it.getParcelable<ParcelableMap<String, Int>>(KEY_TOP)?.map
originHeightMap = it.getParcelable<ParcelableMap<String, Int>>(KEY_HEIGHT)?.map
originWidthMap = it.getParcelable<ParcelableMap<String, Int>>(KEY_WIDTH)?.map
}
viewModel?.selectedPreviewDataListLiveData?.observe(viewLifecycleOwner) { list ->
if (list.isNullOrEmpty()) {
quickSelectRecyclerView?.visibility = View.GONE
} else {
quickSelectRecyclerView?.visibility = View.VISIBLE
if (quickSelectRecyclerView?.adapter == null) {
quickSelectRecyclerView?.layoutManager =
LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
quickSelectAdapter =
QuickSelectAdapter(requireContext(), parentViewModel?.getAlbumId() ?: "", viewModel!!)
quickSelectRecyclerView?.adapter = quickSelectAdapter
}
quickSelectAdapter?.submitList(list)
}
binding.mediaControl.previewTv.isEnabled = list.isNotEmpty()
binding.mediaControl.confirmTv.isEnabled = list.isNotEmpty()
if (list.isNotEmpty()) {
binding.mediaControl.previewTv.setTextColor(R.color.text_instance.toColor(requireContext()))
binding.mediaControl.confirmTv.text = "确定(${list.size})"
binding.mediaControl.confirmTv.alpha = 1f
} else {
binding.mediaControl.previewTv.setTextColor(R.color.text_secondary.toColor(requireContext()))
binding.mediaControl.confirmTv.text = "确定"
binding.mediaControl.confirmTv.alpha = 0.6f
}
binding.mediaControl.confirmTv.setOnClickListener {
parentViewModel?.postSelectedResult()
}
}
viewModel?.positionLiveData?.observe(viewLifecycleOwner) {
viewPager.setCurrentItem(it, false)
}
binding.checkedIv.setOnClickListener { binding.checkedContainer.performClick() }
binding.checkedContainer.enlargeTouchArea(10F.dip2px())
binding.checkedContainer.setOnClickListener {
if (currentDisplayingItem == null) return@setOnClickListener
if (parentViewModel?.isSelected(currentDisplayingItem!!) == true) {
parentViewModel?.removeSelection(currentDisplayingItem!!)
} else {
if (parentViewModel?.addSelection(currentDisplayingItem!!) == false) {
ToastUtils.showToast("至多选择${parentViewModel?.maxSelectionSize}张图片")
}
}
updateSelectStatus()
}
binding.mediaControl.previewTv.visibility = View.GONE
setAdapterAndPerformEnterAnimation()
if (album != null) {
initEnterAnimation()
}
DisplayUtils.transparentStatusAndNavigation(requireActivity())
}
private fun setAdapterAndPerformEnterAnimation() {
viewPagerAdapter = ViewImageAdapter()
viewPager.addOnPageChangeListener(this)
viewPager.adapter = viewPagerAdapter
viewPager.currentItem = positionInAlbum
if (positionInAlbum == 0) {
onPageSelected(0)
}
currentDisplayingItem = parentViewModel?.getMediaItem(positionInAlbum, isSelectedOnly())
}
override fun onHandleBackPressed(): Boolean {
updateOriginPosition(currentDisplayingItem?.uri?.toString() ?: "", viewPager.currentItem)
finishWithAnimation()
return true
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(EntranceConsts.KEY_CURRENTITEM, viewPager.currentItem)
}
/**
* 更新动画还原时的位置
*
* @param url 图片地址
* @param position 当前位置
*/
private fun updateOriginPosition(url: String, position: Int) {
val left = originLeftMap?.get(url)
val top = originTopMap?.get(url)
val height = originHeightMap?.get(url)
val width = originWidthMap?.get(url)
if (left == null || top == null || height == null || width == null) {
return
}
container = containerMap[position]
if (container != null) {
bigImageView = containerMap[position].findViewById(R.id.iv)
}
originLeft = left
originTop = top
originHeight = height
originWidth = width
resizeImage(url)
originCenterX = originLeft + originWidth / 2
originCenterY = originTop + originHeight / 2
originHeightWidthRatio = originHeight.toFloat() / originWidth
scaleX = originWidth.toFloat() / targetWidth
scaleY = scaleX
translationX = originCenterX - targetCenterX
translationY = originCenterY - targetCenterY
}
// 处理非等比例缩放的图片
private fun resizeImage(url: String) {
bigImageView?.ssiv?.run {
imageRatio = sWidth / sHeight.toFloat()
viewRatio = originWidth / originHeight.toFloat()
if (!shouldResize()) return
if (viewRatio < imageRatio) {
originWidth = (originHeight * imageRatio).round2Int()
originLeft -= (originWidth - (originWidthMap?.get(url) ?: 0)) / 2
} else {
originHeight = (originWidth / imageRatio).round2Int()
if (justChangeBoundsAndTransform()) return
originTop -= (originHeight - (originHeightMap?.get(url) ?: 0)) / 2
}
}
}
private fun Float.round2Int() = if (!isNaN()) roundToInt() else 0
override fun onPageSelected(position: Int) {
currentDisplayingItem = parentViewModel?.getMediaItem(position, isSelectedOnly())
currentDisplayingItem?.let {
viewModel?.highlightItem(it)
parentViewModel?.indexOfSelected(it)?.let { quickPosition ->
if (quickPosition == -1) return@let
quickSelectRecyclerView?.doOnLayout {
quickSelectRecyclerView?.smoothScrollToPosition(quickPosition)
}
}
}
updateSelectStatus()
}
@SuppressLint("SetTextI18n")
override fun onPageScrolled(
position: Int, positionOffset: Float,
positionOffsetPixels: Int
) {
}
override fun onPageScrollStateChanged(newState: Int) {}
private fun isSelectedOnly(): Boolean = album == null
private fun updateSelectStatus() {
currentDisplayingItem?.let {
if (parentViewModel?.isSelected(it) == true) {
if (parentViewModel?.maxSelectionSize != 1) {
binding.checkedIv.setImageResource(R.drawable.ic_choose_media_bg)
binding.chooseCountTv.visibility = View.VISIBLE
binding.chooseCountTv.text = "${(parentViewModel?.indexOfSelected(it) ?: 0) + 1}"
} else {
binding.chooseCountTv.visibility = View.GONE
binding.checkedIv.setImageResource(R.drawable.ic_toolbar_checked)
}
} else {
binding.chooseCountTv.visibility = View.GONE
binding.checkedIv.setImageResource(R.drawable.ic_toolbar_check)
}
}
}
private fun initEnterAnimation() {
viewPager.viewTreeObserver
.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewPager.viewTreeObserver.removeOnGlobalLayoutListener(this)
originLeft = originLeftMap?.get(previewItem?.uri?.toString()) ?: 0
originTop = originTopMap?.get(previewItem?.uri?.toString()) ?: 0
originHeight = originHeightMap?.get(previewItem?.uri?.toString()) ?: 0
originWidth = originWidthMap?.get(previewItem?.uri?.toString()) ?: 0
originCenterX = originLeft + originWidth / 2
originCenterY = originTop + originHeight / 2
originHeightWidthRatio = originHeight.toFloat() / originWidth
val location = IntArray(2)
bigImageView?.getLocationOnScreen(location)
targetHeight = bigImageView?.height?.toFloat() ?: 0F
targetWidth = bigImageView?.width?.toFloat() ?: 0F
scaleX =
if (originWidth == 0 && targetWidth == 0F) 1F else originWidth.toFloat() / targetWidth
scaleY = scaleX
targetCenterX = location[0] + targetWidth / 2
targetCenterY = location[1] + targetHeight / 2
translationX = originCenterX - targetCenterX
translationY = originCenterY - targetCenterY
if (useEnterAndExitAnimation) {
bigImageView?.translationX = translationX
bigImageView?.translationY = translationY
if (!scaleX.isNaN() && scaleX < Float.MAX_VALUE) {
bigImageView?.scaleX = scaleX
bigImageView?.scaleY = scaleY
}
}
performEnterAnimation()
}
})
}
private fun loadImage(
uri: Uri?,
imageView: BigImageView
) {
// 添加GIF支持
imageView.setImageViewFactory(FrescoImageViewFactory())
imageView.setThumbnailScaleType(ImageView.ScaleType.FIT_CENTER)
imageView.showImage(uri)
}
private fun finishWithAnimation() {
val fadeOnly = isFadeOnly()
val animatorSet = AnimatorSet()
val translateXAnimator = ValueAnimator.ofFloat(0F, translationX).apply {
addUpdateListener { va -> bigImageView?.x = (va.animatedValue as Float) }
}
val translateYAnimator = ValueAnimator.ofFloat(0F, translationY).apply {
addUpdateListener { va -> bigImageView?.y = (va.animatedValue as Float) }
}
val scaleYAnimator = ValueAnimator.ofFloat(1F, scaleY).apply {
addUpdateListener { va ->
if (va.animatedValue is Float && !(va.animatedValue as Float).isNaN() && (va.animatedValue as Float) < Float.MAX_VALUE) {
bigImageView?.scaleY = (va.animatedValue as Float)
}
}
}
val scaleXAnimator = ValueAnimator.ofFloat(1F, scaleX).apply {
addUpdateListener { va ->
if (va.animatedValue is Float && !(va.animatedValue as Float).isNaN() && (va.animatedValue as Float) < Float.MAX_VALUE) {
bigImageView?.scaleX = (va.animatedValue as Float)
}
}
}
val backgroundAlphaAnimation = ValueAnimator.ofFloat(1F, 0F).apply {
addUpdateListener { va -> backgroundView.alpha = (va.animatedValue as Float) }
}
val alphaAnimator = ValueAnimator.ofFloat(1F, 0F).apply {
addUpdateListener { va -> viewPager.alpha = (va.animatedValue as Float) }
}
if (useEnterAndExitAnimation && !fadeOnly) {
if (currentDisplayingItem?.isGif == true) {
animatorSet.apply {
playTogether(
translateXAnimator,
translateYAnimator,
scaleXAnimator,
scaleYAnimator,
backgroundAlphaAnimation
)
duration = ANIMATION_DURATION
doOnStart {
bottomContainer.visibility = View.GONE
toolbarContainer.visibility = View.GONE
}
doOnEnd {
it.removeAllListeners()
parentFragmentManager.popBackStack()
}
}.start()
} else {
startEndTransition()
}
} else {
animatorSet.apply {
playTogether(alphaAnimator, backgroundAlphaAnimation)
duration = ANIMATION_DURATION
doOnStart {
bottomContainer.visibility = View.GONE
toolbarContainer.visibility = View.GONE
}
doOnEnd {
it.removeAllListeners()
parentFragmentManager.popBackStack()
}
}.start()
}
}
private fun shouldResize() = abs(imageRatio - viewRatio) > RATIO_DIFF
private fun justChangeBoundsAndTransform() =
imageRatio < viewRatio && viewPagerAdapter?.count == 1
private fun startEndTransition() {
if (animating) return
val doResizeTransition = Runnable {
TransitionManager.beginDelayedTransition(
container as ViewGroup,
TransitionSet().apply {
addTransition(ChangeBounds())
addTransition(ChangeTransform())
if (!justChangeBoundsAndTransform()) addTransition(ResizeTransition())
duration = RESIZE_DURATION
interpolator = DecelerateInterpolator()
addListener(object : TransitionListenerAdapter() {
override fun onTransitionStart(transition: Transition) {
animating = true
}
override fun onTransitionEnd(transition: Transition) {
if (!animating) return
animating = false
parentFragmentManager.popBackStack()
}
})
})
bigImageView?.ssiv?.run {
layoutParams = layoutParams.apply {
if (viewRatio < imageRatio) {
width = originWidthMap?.get(currentDisplayingItem?.uri?.toString()) ?: 0
} else {
height = originHeightMap?.get(currentDisplayingItem?.uri?.toString()) ?: 0
}
if (this is ViewGroup.MarginLayoutParams) {
if (viewRatio < imageRatio) {
leftMargin =
originLeftMap?.get(currentDisplayingItem?.uri?.toString()) ?: 0
} else {
topMargin =
originTopMap?.get(currentDisplayingItem?.uri?.toString()) ?: 0
}
}
}
}
}
val doTransition = Runnable {
TransitionManager.beginDelayedTransition(
container as ViewGroup,
TransitionSet().apply {
addTransition(ChangeBounds())
addTransition(ChangeTransform())
addTransition(SsivTransition())
duration = ANIMATION_DURATION
interpolator = DecelerateInterpolator()
addListener(object : TransitionListenerAdapter() {
override fun onTransitionStart(transition: Transition) {
animating = true
bottomContainer.visibility = View.GONE
toolbarContainer.visibility = View.GONE
}
override fun onTransitionEnd(transition: Transition) {
if (!animating) return
animating = false
if (shouldResize()) {
container?.post(doResizeTransition)
} else {
parentFragmentManager.popBackStack()
}
}
})
})
backgroundView.animate().setDuration(ANIMATION_DURATION).alpha(0F).start()
bigImageView?.run {
scaleX = 1F
scaleY = 1F
translationX = 0F
translationY = 0F
}
bigImageView?.ssiv?.run {
layoutParams = layoutParams.apply {
width = originWidth
height = originHeight
if (this is ViewGroup.MarginLayoutParams) {
leftMargin = originLeft
topMargin = originTop
}
}
}
}
container?.post(doTransition)
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
lifecycle.removeObserver(this)
animating = false
container?.removeCallbacks(doTransition)
if (shouldResize()) container?.removeCallbacks(doResizeTransition)
TransitionManager.endTransitions(container as ViewGroup)
}
}
})
}
private fun performEnterAnimation() {
val animatorSet = AnimatorSet()
val translateXAnimator = ValueAnimator.ofFloat(bigImageView!!.x, 0F).apply {
addUpdateListener { va -> bigImageView?.x = (va.animatedValue as Float) }
}
val translateYAnimator = ValueAnimator.ofFloat(bigImageView!!.y, 0F).apply {
addUpdateListener { va -> bigImageView?.y = (va.animatedValue as Float) }
}
val scaleYAnimator = ValueAnimator.ofFloat(scaleY, 1F).apply {
addUpdateListener { va ->
if (va.animatedValue is Float && !(va.animatedValue as Float).isNaN() && (va.animatedValue as Float) < Float.MAX_VALUE) {
bigImageView?.scaleY = (va.animatedValue as Float)
}
}
}
val scaleXAnimator = ValueAnimator.ofFloat(scaleX, 1F).apply {
addUpdateListener { va ->
if (va.animatedValue is Float && !(va.animatedValue as Float).isNaN() && (va.animatedValue as Float) < Float.MAX_VALUE) {
bigImageView?.scaleX = (va.animatedValue as Float)
}
}
}
val backgroundAlphaAnimator = ValueAnimator.ofFloat(0F, 1F).apply {
addUpdateListener { va -> backgroundView.alpha = (va.animatedValue as Float) }
}
animatorSet.apply {
if (useEnterAndExitAnimation) {
playTogether(
translateXAnimator,
translateYAnimator,
scaleXAnimator,
scaleYAnimator,
backgroundAlphaAnimator
)
} else {
playTogether(backgroundAlphaAnimator)
}
duration = ANIMATION_DURATION
doOnStart {
bottomContainer.visibility = View.GONE
toolbarContainer.visibility = View.GONE
}
doOnEnd {
bottomContainer.visibility = View.VISIBLE
toolbarContainer.visibility = View.VISIBLE
}
}.start()
}
private fun performExitAnimation(view: View?, scale: Float, fadeOnly: Boolean) {
if (view == null) {
parentFragmentManager.popBackStack()
return
}
val finalScale = originWidth / targetWidth
val finalTranslationX = originLeft - (1 - finalScale) * targetWidth / 2
val finalTranslationY =
originTop - ((1 - finalScale) * targetHeight + (targetHeight * finalScale - originHeight)) / 2
val animatorSet = AnimatorSet()
val scaleAnimator = ValueAnimator.ofFloat(scale, finalScale).apply {
addUpdateListener { va ->
if (va.animatedValue is Float && !(va.animatedValue as Float).isNaN() && (va.animatedValue as Float) < Float.MAX_VALUE) {
view.scaleX = (va.animatedValue as Float)
view.scaleY = (va.animatedValue as Float)
}
}
}
val translateXAnimator = ValueAnimator.ofFloat(view.x, finalTranslationX).apply {
addUpdateListener { va -> view.translationX = (va.animatedValue as Float) }
}
val translateYAnimator = ValueAnimator.ofFloat(view.y, finalTranslationY).apply {
addUpdateListener { va -> view.translationY = (va.animatedValue as Float) }
}
val backgroundAlphaAnimator = ValueAnimator.ofFloat(backgroundView.alpha, 0F).apply {
addUpdateListener { va -> backgroundView.alpha = (va.animatedValue as Float) }
}
val alphaAnimator = ValueAnimator.ofFloat(1F, 0F).apply {
addUpdateListener { va -> view.alpha = (va.animatedValue as Float) }
}
if (useEnterAndExitAnimation && !fadeOnly) {
if (currentDisplayingItem?.isGif == true) {
animatorSet.apply {
playTogether(
scaleAnimator,
translateXAnimator,
translateYAnimator,
backgroundAlphaAnimator
)
doOnEnd {
it.removeAllListeners()
parentFragmentManager.popBackStack()
}
interpolator = DecelerateInterpolator()
duration = ANIMATION_DURATION
}.start()
} else {
startEndTransition()
}
} else {
animatorSet.apply {
playTogether(backgroundAlphaAnimator, alphaAnimator)
doOnEnd {
it.removeAllListeners()
parentFragmentManager.popBackStack()
}
interpolator = DecelerateInterpolator()
duration = ANIMATION_DURATION
}.start()
}
}
private inner class ResizeTransition : Transition() {
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
private fun captureValues(transitionValues: TransitionValues) {
transitionValues.values["width"] = transitionValues.view.width
transitionValues.values["height"] = transitionValues.view.height
}
override fun createAnimator(
sceneRoot: ViewGroup,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
val startWidth = (startValues?.values?.get("width") as Int?) ?: 0
val endWidth = (endValues?.values?.get("width") as Int?) ?: 0
val startHeight = (startValues?.values?.get("height") as Int?) ?: 0
val endHeight = (endValues?.values?.get("height") as Int?) ?: 0
val firstValue =
if (viewRatio < imageRatio) startWidth.toFloat() else startHeight.toFloat()
val secondValue =
if (viewRatio < imageRatio) endWidth.toFloat() else endHeight.toFloat()
return ObjectAnimator.ofFloat(firstValue, secondValue).apply {
addUpdateListener {
sceneRoot.findViewById<BigImageView>(R.id.iv)?.ssiv?.run {
maxScale = this.scale
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP)
resetScaleAndCenter()
}
}
}
}
}
private inner class SsivTransition : Transition() {
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
private fun captureValues(transitionValues: TransitionValues) {
transitionValues.values["width"] = transitionValues.view.width
transitionValues.values["height"] = transitionValues.view.height
}
override fun createAnimator(
sceneRoot: ViewGroup,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
val startHeight = (startValues?.values?.get("height") as Int?) ?: 0
val endHeight = (endValues?.values?.get("height") as Int?) ?: 0
return ObjectAnimator.ofFloat(startHeight.toFloat(), endHeight.toFloat()).apply {
addUpdateListener {
sceneRoot.findViewById<BigImageView>(R.id.iv)?.ssiv?.run {
setScaleAndCenter(minScale, PointF(sWidth / 2F, sHeight / 2F))
}
}
}
}
}
private inner class ViewImageAdapter : PagerAdapter() {
override fun getCount() = parentViewModel?.getMediaCount(isSelectedOnly()) ?: 0
@SuppressLint("MissingPermission")
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val item = parentViewModel?.getMediaItem(position, isSelectedOnly())
val view = View.inflate(container.context, R.layout.local_media_preview_item, null) as ViewGroup
val imageView: DraggableBigImageView = view.findViewById(R.id.iv)
if (useEnterAndExitAnimation) {
containerMap.put(position, view)
}
if (bigImageView == null) {
bigImageView = imageView
}
imageView.setDragListener(object : DraggableBigImageView.DragListener {
override fun onRelease(draggableBigImageView: DraggableBigImageView, scale: Float) {
updateOriginPosition(currentDisplayingItem?.uri?.toString() ?: "", viewPager.currentItem)
performExitAnimation(draggableBigImageView, scale, isFadeOnly())
}
override fun onDrag(draggableBigImageView: DraggableBigImageView, fraction: Float) {
backgroundView.alpha = 1 - fraction
bottomContainer.visibility = View.GONE
toolbarContainer.visibility = View.GONE
}
override fun onRestore(
draggableBigImageView: DraggableBigImageView,
fraction: Float
) {
backgroundView.alpha = 1F
bottomContainer.visibility = View.VISIBLE
toolbarContainer.visibility = View.VISIBLE
}
})
imageView.setOnClickListener {
if (bottomContainer.visibility == View.VISIBLE) {
bottomContainer.visibility = View.GONE
toolbarContainer.visibility = View.GONE
} else {
bottomContainer.visibility = View.VISIBLE
toolbarContainer.visibility = View.VISIBLE
}
}
imageView.setImageLoaderCallback(object : SimpleImageLoader() {
override fun onSuccess(image: File) {
val ssiv = imageView.ssiv
if (ssiv != null) {
ssiv.maxScale = 10f // 这个缩放倍数最好很具宽高自动调节
}
}
})
loadImage(item?.uri, imageView)
view.tag = position
container.addView(view)
return view
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
container.removeView(`object` as View)
}
override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view === `object`
}
}
private fun isFadeOnly() = originLeft == 0 || originTop == 0
companion object {
const val RATIO_DIFF = 0.01F
private const val KEY_USE_ENTER_AND_EXIT_ANIMATION = "use_enter_and_exit_animation"
const val KEY_ITEM_LIST = "item_list"
const val KEY_ALBUM = "album"
const val KEY_POSITION_IN_ALBUM = "position_in_album"
const val KEY_PREVIEW_ITEM = "preview_item"
private const val KEY_LEFT = "left"
private const val KEY_TOP = "top"
private const val KEY_HEIGHT = "height"
private const val KEY_WIDTH = "width"
private const val ANIMATION_DURATION = 350L
private const val RESIZE_DURATION = 100L
/**
* 传入 viewList 代表使用渐入渐出动画
*/
fun getInstance(
selectedItemList: ArrayList<Item>,
album: Album?,
positionInAlbum: Int = 0,
previewItem: Item? = null,
originalUrlAndViewMap: Map<String, View>? = null,
): LocalMediaPreviewFragment {
val bundle = Bundle()
bundle.putParcelableArrayList(KEY_ITEM_LIST, selectedItemList)
bundle.putParcelable(KEY_ALBUM, album)
bundle.putInt(KEY_POSITION_IN_ALBUM, positionInAlbum)
bundle.putParcelable(KEY_PREVIEW_ITEM, previewItem)
if (originalUrlAndViewMap != null) {
val leftMap = hashMapOf<String, Int>()
val topMap = hashMapOf<String, Int>()
val heightMap = hashMapOf<String, Int>()
val widthMap = hashMapOf<String, Int>()
for (entry in originalUrlAndViewMap) {
val location = IntArray(2)
val url = entry.key
val view = entry.value
view.getLocationOnScreen(location)
leftMap.put(url, location[0])
topMap.put(url, location[1])
heightMap.put(url, view.height)
widthMap.put(url, view.width)
}
bundle.putParcelable(KEY_LEFT, ParcelableMap(leftMap))
bundle.putParcelable(KEY_TOP, ParcelableMap(topMap))
bundle.putParcelable(KEY_HEIGHT, ParcelableMap(heightMap))
bundle.putParcelable(KEY_WIDTH, ParcelableMap(widthMap))
bundle.putBoolean(KEY_USE_ENTER_AND_EXIT_ANIMATION, true)
}
return LocalMediaPreviewFragment().also { it.with(bundle) }
}
}
}

View File

@ -0,0 +1,59 @@
package com.gh.gamecenter.feature.selector
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.zhihu.matisse.internal.entity.Item
import kotlinx.coroutines.launch
class LocalMediaPreviewViewModel(private val localMediaRepo: LocalMediaRepository) : ViewModel() {
private var highlightedItem: Item? = null
private var _positionLiveData: MutableLiveData<Int> = MutableLiveData()
val positionLiveData: LiveData<Int> = _positionLiveData
private val _selectedPreviewDataListLiveData by lazy { MutableLiveData<List<SelectedPreviewData>>() }
val selectedPreviewDataListLiveData: LiveData<List<SelectedPreviewData>> = _selectedPreviewDataListLiveData
init {
viewModelScope.launch {
localMediaRepo.selectedItemListFlow.collect { itemList ->
emitSelectedPreviewData(itemList)
}
}
}
fun scrollToPosition(position: Int) {
_positionLiveData.value = position
}
fun highlightItem(item: Item) {
highlightedItem = item
emitSelectedPreviewData(localMediaRepo.selectedItemListFlow.value)
}
fun emitSelectedPreviewData(itemList: List<Item>) {
// Optionally modify each item
val newSelectedPreviewDataList = arrayListOf<SelectedPreviewData>()
for (item in itemList) {
newSelectedPreviewDataList.add(
SelectedPreviewData(item.contentUri == highlightedItem?.contentUri, item)
)
}
// Emit the updated list
_selectedPreviewDataListLiveData.postValue(newSelectedPreviewDataList)
}
class Factory(
private val localMediaRepo: LocalMediaRepository
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LocalMediaPreviewViewModel(localMediaRepo) as T
}
}
}

View File

@ -0,0 +1,34 @@
package com.gh.gamecenter.feature.selector
import com.zhihu.matisse.internal.entity.Item
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class LocalMediaRepository {
private val _selectedItemListFlow = MutableStateFlow<ArrayList<Item>>(arrayListOf())
val selectedItemListFlow: StateFlow<ArrayList<Item>> = _selectedItemListFlow
private val _selectedMediaList = ArrayList<Item>()
val selectedMediaList
get() = _selectedMediaList
suspend fun addSelection(item: Item) {
_selectedMediaList.add(item)
_selectedItemListFlow.emit(ArrayList(_selectedMediaList))
}
suspend fun removeSelection(item: Item) {
_selectedMediaList.remove(item)
_selectedItemListFlow.emit(ArrayList(_selectedMediaList))
}
fun isSelected(item: Item): Boolean {
return _selectedMediaList.contains(item)
}
fun indexOfSelected(item: Item): Int {
return _selectedMediaList.indexOf(item)
}
}

View File

@ -0,0 +1,120 @@
package com.gh.gamecenter.feature.selector
import android.database.Cursor
import android.security.identity.ResultData
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.zhihu.matisse.internal.entity.Album
import com.zhihu.matisse.internal.entity.Item
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
class LocalMediaViewModel(val maxSelectionSize: Int, val localMediaRepo: LocalMediaRepository) : ViewModel() {
private val _previewImageLiveData = MutableLiveData<PreviewData>()
val previewImageLiveData: LiveData<PreviewData> = _previewImageLiveData
private val _resultLiveData = MutableLiveData<Boolean>()
val resultLiveData: LiveData<Boolean> = _resultLiveData
val selectedCountLiveData: LiveData<Int> = localMediaRepo.selectedItemListFlow.map { it.size }.asLiveData()
val selectedMediaListLiveData: LiveData<ArrayList<Item>> = localMediaRepo.selectedItemListFlow.asLiveData()
private var albumCursor: Cursor? = null
val selectedMediaList
get() = localMediaRepo.selectedMediaList
private var mediaAlbum: Album? = null
fun getAlbumId(): String? {
return mediaAlbum?.id
}
fun updateAlbum(album: Album) {
mediaAlbum = album
}
// TODO 记得在 onDestroy 时关闭 cursor
fun updateAlbumCursor(cursor: Cursor) {
albumCursor = cursor
}
fun previewImage(
onlySelected: Boolean,
positionInAlbum: Int = 0,
previewItem: Item? = null,
urlAndViewMap: Map<String, android.view.View> = emptyMap()
) {
if (onlySelected) {
_previewImageLiveData.value = PreviewData(localMediaRepo.selectedMediaList, null, 0, previewItem, urlAndViewMap)
} else {
_previewImageLiveData.value =
PreviewData(localMediaRepo.selectedMediaList, mediaAlbum, positionInAlbum, previewItem, urlAndViewMap)
}
}
fun addSelection(item: Item): Boolean {
if (localMediaRepo.selectedMediaList.size >= maxSelectionSize) {
return false
}
viewModelScope.launch {
localMediaRepo.addSelection(item)
}
return true
}
fun removeSelection(item: Item) {
viewModelScope.launch {
localMediaRepo.removeSelection(item)
}
}
fun isSelected(item: Item): Boolean {
return localMediaRepo.selectedMediaList.contains(item)
}
fun indexOfSelected(item: Item): Int {
return localMediaRepo.selectedMediaList.indexOf(item)
}
fun getMediaCount(selectedOnly: Boolean): Int {
return if (selectedOnly) {
selectedMediaList.size
} else {
albumCursor?.count ?: 0
}
}
fun getMediaItem(position: Int, selectedOnly: Boolean): Item {
return if (selectedOnly) {
selectedMediaList[position]
} else {
albumCursor?.moveToPosition(position)
Item.valueOf(albumCursor).also {
it.positionInAlbum = position
it.albumId = getAlbumId()
}
}
}
fun postSelectedResult() {
_resultLiveData.postValue(true)
}
class Factory(
private val maxSelectionSize: Int,
private val localMediaRepo: LocalMediaRepository
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LocalMediaViewModel(maxSelectionSize, localMediaRepo) as T
}
}
}

View File

@ -0,0 +1,13 @@
package com.gh.gamecenter.feature.selector
import android.view.View
import com.zhihu.matisse.internal.entity.Album
import com.zhihu.matisse.internal.entity.Item
class PreviewData(
val previewList: ArrayList<Item>,
val album: Album?,
val positionInAlbum: Int,
val previewItem: Item?,
val urlAndViewMap: Map<String, View>
)

View File

@ -0,0 +1,68 @@
package com.gh.gamecenter.feature.selector
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.baselist.DiffUtilAdapter
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.feature.databinding.ItemLocalMediaPreviewQuickSelectBinding
internal class QuickSelectAdapter(
context: Context,
private val currentAlbumId: String,
private val viewModel: LocalMediaPreviewViewModel
) : DiffUtilAdapter<SelectedPreviewData>(context) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder {
return ItemViewHolder(parent.toBinding())
}
override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int
) {
val item = mDataList[position]
if (holder is ItemViewHolder) {
holder.bindView(item)
holder.binding.root.setOnClickListener {
// 跨 album 的图片不允许快速跳转
if (currentAlbumId != item.item.albumId) return@setOnClickListener
val targetPosition = if (item.item.positionInAlbum != -1) {
item.item.positionInAlbum
} else {
0
}
viewModel.scrollToPosition(targetPosition)
}
}
}
override fun getItemCount() = mDataList.size
override fun areItemsTheSame(oldItem: SelectedPreviewData?, newItem: SelectedPreviewData?): Boolean {
return oldItem?.item?.contentUri == newItem?.item?.contentUri
}
override fun areContentsTheSame(oldItem: SelectedPreviewData?, newItem: SelectedPreviewData?): Boolean {
return oldItem?.isHighlighted == newItem?.isHighlighted
&& oldItem?.item?.contentUri == newItem?.item?.contentUri
}
private class ItemViewHolder(var binding: ItemLocalMediaPreviewQuickSelectBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindView(item: SelectedPreviewData) {
ImageUtils.displayResizeMedia(binding.thumbnailIv, item.item.contentUri, 56, 56)
binding.selectedRingView.goneIf(!item.isHighlighted)
}
}
}

View File

@ -0,0 +1,5 @@
package com.gh.gamecenter.feature.selector
import com.zhihu.matisse.internal.entity.Item
class SelectedPreviewData(val isHighlighted: Boolean, val item: Item)

View File

@ -1,4 +1,4 @@
package com.gh.gamecenter.qa.editor
package com.gh.gamecenter.feature.selector
import android.content.Context
import android.database.Cursor
@ -7,12 +7,17 @@ import android.view.View
import android.view.ViewGroup
import android.widget.CursorAdapter
import android.widget.TextView
import com.gh.gamecenter.common.R
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.goneIf
import com.zhihu.matisse.internal.entity.Album
class VideoAlbumsAdapter(context: Context) : CursorAdapter(context, null) {
private var selectedPosition = 0
override fun newView(context: Context?, cursor: Cursor?, parent: ViewGroup?): View {
val inflate = LayoutInflater.from(context)
return inflate.inflate(R.layout.video_albums_item, parent, false)
@ -20,8 +25,14 @@ class VideoAlbumsAdapter(context: Context) : CursorAdapter(context, null) {
override fun bindView(view: View, context: Context, cursor: Cursor?) {
val album = Album.valueOf(cursor)
view.findViewById<View>(R.id.checkIv).goneIf(cursor?.position != selectedPosition)
view.findViewById<TextView>(R.id.album_name).text = album.getDisplayName(context)
view.findViewById<TextView>(R.id.album_media_count).text = album.count.toString()
view.findViewById<SimpleDraweeView>(R.id.album_cover).setImageURI(album.coverUri)
}
fun updateSelectedPosition(position: Int) {
selectedPosition = position
}
}

View File

@ -1,4 +1,4 @@
package com.gh.gamecenter.qa.editor
package com.gh.gamecenter.feature.selector
import android.content.Context
import android.graphics.drawable.ColorDrawable
@ -11,11 +11,13 @@ import android.widget.PopupWindow
import androidx.appcompat.widget.ListPopupWindow
import androidx.core.content.ContextCompat
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.R
import com.gh.gamecenter.common.R
class VideoAlbumsSpanner(val context: Context) {
private var mListPopupWindow: ListPopupWindow = CustomListPopupWindow(context, R.style.PosterListPopupWindow)
private var mListPopupWindow: ListPopupWindow =
CustomListPopupWindow(context, R.style.PosterListPopupWindow)
private lateinit var mAdapter: CursorAdapter
@ -28,7 +30,14 @@ class VideoAlbumsSpanner(val context: Context) {
mListPopupWindow.dismiss()
onItemSelectedListener?.onItemSelected(parent, view, position, id)
}
mListPopupWindow.setBackgroundDrawable(ColorDrawable(ContextCompat.getColor(context, R.color.transparent)))
mListPopupWindow.setBackgroundDrawable(
ColorDrawable(
ContextCompat.getColor(
context,
R.color.transparent
)
)
)
}
fun setDismissListener(listener: PopupWindow.OnDismissListener) {
@ -52,8 +61,8 @@ class VideoAlbumsSpanner(val context: Context) {
mListPopupWindow.show()
val containerView = mListPopupWindow.listView as? ViewGroup
val params = containerView?.layoutParams as ViewGroup.LayoutParams
params.height = 280f.dip2px()
containerView.background = ColorDrawable(ContextCompat.getColor(context, R.color.ui_surface))
params.height = 280F.dip2px()
containerView.background = ColorDrawable(ContextCompat.getColor(context, R.color.ui_surface_fixed_dark))
containerView.layoutParams = params
val parentContainer = containerView.parent as FrameLayout

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,35 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- First Stroke Layer -->
<item>
<shape android:shape="rectangle">
<corners android:radius="4dp" />
<stroke
android:width="3dp"
android:color="@color/black" /> <!-- Second stroke color -->
</shape>
</item>
<!-- Second Stroke Layer -->
<item>
<shape android:shape="rectangle">
<corners android:radius="4dp" />
<stroke
android:width="2dp"
android:color="@color/primary_theme" /> <!-- First stroke color -->
</shape>
</item>
<!-- Transparent Content Layer -->
<item
android:bottom="4dp"
android:left="4dp"
android:right="4dp"
android:top="4dp">
<shape android:shape="rectangle">
<corners android:radius="2dp" />
<solid android:color="@android:color/transparent" />
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<com.gh.gamecenter.common.view.MaterializedConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/ui_background_fixed_dark"
android:orientation="vertical">
<com.gh.gamecenter.common.view.StatusBarView
android:id="@+id/statusBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/ui_surface_fixed_dark"/>
<RelativeLayout
android:id="@+id/normal_toolbar_container"
android:layout_width="match_parent"
android:layout_height="@dimen/appbar_height"
app:layout_constraintTop_toBottomOf="@id/statusBar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/normal_toolbar"
style="@style/Base_ToolbarStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/ui_surface_fixed_dark"
app:contentInsetEnd="0dp"
app:contentInsetStartWithNavigation="0dp"
app:navigationIcon="@null">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/backContainer"
android:layout_width="48dp"
android:layout_height="48dp">
<ImageView
android:id="@+id/backBtn"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
app:srcCompat="@drawable/ic_function_close" />
</FrameLayout>
<TextView
android:id="@+id/normal_title"
style="@style/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:textColor="@color/text_aw_primary"
android:textSize="16sp" />
<ImageView
android:id="@+id/arrowIv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="-42dp"
android:layout_toRightOf="@+id/normal_title"
app:srcCompat="@drawable/ic_pin_down" />
</RelativeLayout>
</androidx.appcompat.widget.Toolbar>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/ui_background_fixed_dark"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/normal_toolbar_container">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/list_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_rv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<include
android:id="@+id/pieceMediaControl"
layout="@layout/piece_media_control"/>
</LinearLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include
android:id="@+id/reuse_ll_loading"
layout="@layout/reuse_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<include
android:id="@+id/reuse_no_connection"
layout="@layout/reuse_no_connection" />
<include
android:id="@+id/reuse_none_data"
layout="@layout/reuse_none_data" />
<include
android:id="@+id/reuse_data_exception"
layout="@layout/reuse_data_exception" />
</RelativeLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/layout_activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.gh.gamecenter.common.view.MaterializedConstraintLayout>

View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<View
android:id="@+id/background_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black" />
<com.lightgame.view.NoScrollableViewPager
android:id="@+id/image_detail_page"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never" />
<LinearLayout
android:id="@+id/toolbarContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.gh.gamecenter.common.view.StatusBarView
android:id="@+id/statusBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/ui_surface_fixed_dark" />
<androidx.appcompat.widget.Toolbar
style="@style/Base_ToolbarStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/ui_surface_fixed_dark"
app:contentInsetEnd="0dp"
app:contentInsetStartWithNavigation="0dp"
app:navigationIcon="@null">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/backContainer"
android:layout_width="48dp"
android:layout_height="48dp">
<ImageView
android:id="@+id/backBtn"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
app:srcCompat="@drawable/ic_bar_back_light" />
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/checkedContainer"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true">
<ImageView
android:id="@+id/checkedIv"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_toolbar_check" />
<TextView
android:id="@+id/chooseCountTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:textColor="@color/text_aw_primary"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/checkedIv"
app:layout_constraintEnd_toEndOf="@+id/checkedIv"
app:layout_constraintStart_toStartOf="@+id/checkedIv"
app:layout_constraintTop_toTopOf="@+id/checkedIv" />
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
</androidx.appcompat.widget.Toolbar>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true">
<LinearLayout
android:id="@+id/bottomContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/quickSelectRecyclerView"
android:layout_width="match_parent"
android:layout_height="84dp"
android:background="@color/ui_surface_fixed_dark"
android:clipToPadding="false"
android:paddingStart="12dp"
android:paddingTop="12dp"
android:paddingEnd="12dp"
android:paddingBottom="8dp"
android:visibility="gone" />
<include
android:id="@+id/mediaControl"
layout="@layout/piece_media_control" />
</LinearLayout>
</RelativeLayout>
</RelativeLayout>

View File

@ -0,0 +1,21 @@
<?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"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/thumbnailIv"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_margin="4dp"
app:roundedCornerRadius="4dp" />
<View
android:id="@+id/selectedRingView"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_margin="4dp"
android:background="@drawable/bg_local_media_preview_quick_selected" />
</FrameLayout>

View File

@ -17,7 +17,7 @@
android:id="@+id/closeIv"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_close"
android:src="@drawable/ic_function_close"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@id/imageIv"
app:layout_constraintTop_toTopOf="@id/imageIv" />

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<com.gh.gamecenter.common.view.Gh_RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.gh.gamecenter.common.view.DraggableBigImageView
android:id="@+id/iv"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:initScaleType="fitCenter"
app:optimizeDisplay="true" />
<include
layout="@layout/reuse_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone" />
</com.gh.gamecenter.common.view.Gh_RelativeLayout>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="52dp"
android:background="@color/ui_surface_fixed_dark">
<TextView
android:id="@+id/previewTv"
android:layout_width="56dp"
android:layout_height="28dp"
android:layout_centerVertical="true"
android:layout_marginLeft="16dp"
android:enabled="false"
android:gravity="center"
android:text="预览"
android:textColor="@color/text_instance"
android:textSize="12sp" />
<TextView
android:id="@+id/confirmTv"
android:layout_width="wrap_content"
android:layout_height="28dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="16dp"
android:alpha="0.6"
android:background="@drawable/button_blue_oval"
android:enabled="false"
android:gravity="center"
android:text="确定"
android:textColor="@color/white"
android:textSize="12sp" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/ui_divider_fixed_dark"/>
</RelativeLayout>