预览图片、视频、删除消息、消息通知铃声
This commit is contained in:
parent
db9ad383fa
commit
06f10d3ed3
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="17" />
|
<bytecodeTargetLevel target="21" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -1,6 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
@ -49,6 +49,8 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":baselib"))
|
implementation(project(":baselib"))
|
||||||
|
implementation(project(":medialib"))
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
|
@ -21,25 +21,27 @@
|
|||||||
android:networkSecurityConfig="@xml/network_config"
|
android:networkSecurityConfig="@xml/network_config"
|
||||||
android:roundIcon="@drawable/app_logo"
|
android:roundIcon="@drawable/app_logo"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Aimz_k"
|
android:theme="@style/Anim_fade"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
|
||||||
android:name=".page.activity.LoginActivity"
|
|
||||||
android:exported="false" />
|
|
||||||
<activity
|
|
||||||
android:name=".page.activity.ChatActivity"
|
|
||||||
android:exported="false" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".page.activity.SplashActivity"
|
android:name=".page.activity.SplashActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.Aimz_k">
|
android:theme="@style/Anim_fade">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".page.activity.LoginActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".page.activity.ChatActivity"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".page.activity.MainActivity"
|
android:name=".page.activity.MainActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
@ -2,14 +2,15 @@ package com.tenlionsoft.aimz_k.adapter
|
|||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import com.lxj.xpopup.XPopup
|
||||||
import com.tenlionsoft.aimz_k.databinding.ItemCategoryBinding
|
import com.tenlionsoft.aimz_k.databinding.ItemCategoryBinding
|
||||||
import com.tenlionsoft.aimz_k.model.MsgCategoryBean
|
import com.tenlionsoft.aimz_k.model.MsgCategoryBean
|
||||||
import com.tenlionsoft.aimz_k.viewmodel.MsgViewModel
|
import com.tenlionsoft.aimz_k.viewmodel.MsgViewModel
|
||||||
import com.tenlionsoft.baselib.base.BaseBindingAdapter
|
import com.tenlionsoft.baselib.base.BaseBindingAdapter
|
||||||
|
|
||||||
|
|
||||||
class CategoryAdapter(
|
class CategoryAdapter(
|
||||||
datas: List<MsgCategoryBean>,
|
datas: List<MsgCategoryBean>, private val viewModel: MsgViewModel
|
||||||
private val viewModel: MsgViewModel
|
|
||||||
) : BaseBindingAdapter<MsgCategoryBean, ItemCategoryBinding>(datas) {
|
) : BaseBindingAdapter<MsgCategoryBean, ItemCategoryBinding>(datas) {
|
||||||
|
|
||||||
override fun getItemBinding(parent: ViewGroup, viewType: Int): ItemCategoryBinding {
|
override fun getItemBinding(parent: ViewGroup, viewType: Int): ItemCategoryBinding {
|
||||||
@ -23,6 +24,21 @@ class CategoryAdapter(
|
|||||||
holder.binding.root.setOnClickListener {
|
holder.binding.root.setOnClickListener {
|
||||||
viewModel.onItemClickListener?.onItemClick(list[position])
|
viewModel.onItemClickListener?.onItemClick(list[position])
|
||||||
}
|
}
|
||||||
|
val xPopup = XPopup.Builder(holder.binding.root.context).watchView(holder.binding.root)
|
||||||
|
holder.binding.root.setOnLongClickListener {
|
||||||
|
xPopup.asAttachList(
|
||||||
|
arrayOf("删除该聊天"),
|
||||||
|
intArrayOf(com.tenlionsoft.baselib.R.drawable.ic_del),
|
||||||
|
{ p, _ ->
|
||||||
|
viewModel.onItemLongClickListener?.onItemLongClick(
|
||||||
|
p, list[position]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
).show()
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -58,6 +58,10 @@ interface MsgCategoryDao {
|
|||||||
@Query("DELETE FROM db_category WHERE `senderId`=(:from) AND receiverId=(:to) OR `senderId`=(:to) AND receiverId=(:from)")
|
@Query("DELETE FROM db_category WHERE `senderId`=(:from) AND receiverId=(:to) OR `senderId`=(:to) AND receiverId=(:from)")
|
||||||
suspend fun delChatHistory(from: String?, to: String?)
|
suspend fun delChatHistory(from: String?, to: String?)
|
||||||
|
|
||||||
|
//删除聊天
|
||||||
|
@Query("DELETE FROM db_category WHERE senderId=:senderId")
|
||||||
|
suspend fun delChatBySenderId(senderId: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加消息多个
|
* 添加消息多个
|
||||||
*
|
*
|
||||||
|
@ -87,8 +87,8 @@ interface MsgDao {
|
|||||||
/**
|
/**
|
||||||
* 清空与某个人的聊天记录
|
* 清空与某个人的聊天记录
|
||||||
*/
|
*/
|
||||||
@Query("DELETE FROM db_msg WHERE `senderId`=(:from) AND `receiverId`=(:to) OR `receiverId`=(:to) AND `senderId`=(:from)")
|
@Query("DELETE FROM db_msg WHERE senderId=:from AND receiverId=:to OR senderId=:from AND senderId=:to")
|
||||||
fun delChatHistory(from: String?, to: String?)
|
suspend fun delChatHistory(from: String, to: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加消息多个
|
* 添加消息多个
|
||||||
|
@ -14,12 +14,16 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.atwa.filepicker.core.FilePicker
|
import com.atwa.filepicker.core.FilePicker
|
||||||
|
import com.google.gson.Gson
|
||||||
import com.tenlionsoft.aimz_k.R
|
import com.tenlionsoft.aimz_k.R
|
||||||
import com.tenlionsoft.aimz_k.databinding.ActivityChatBinding
|
import com.tenlionsoft.aimz_k.databinding.ActivityChatBinding
|
||||||
|
import com.tenlionsoft.aimz_k.model.BodyContent
|
||||||
|
import com.tenlionsoft.aimz_k.model.FileDataBean
|
||||||
import com.tenlionsoft.aimz_k.model.MsgBean
|
import com.tenlionsoft.aimz_k.model.MsgBean
|
||||||
import com.tenlionsoft.aimz_k.model.PickerType
|
import com.tenlionsoft.aimz_k.model.PickerType
|
||||||
import com.tenlionsoft.aimz_k.viewmodel.ChatPageViewModel
|
import com.tenlionsoft.aimz_k.viewmodel.ChatPageViewModel
|
||||||
import com.tenlionsoft.baselib.base.BaseActivity
|
import com.tenlionsoft.baselib.base.BaseActivity
|
||||||
|
import com.tenlionsoft.baselib.contacts.NetConfig
|
||||||
import com.tenlionsoft.baselib.contacts.ProjectConfig
|
import com.tenlionsoft.baselib.contacts.ProjectConfig
|
||||||
import com.tenlionsoft.baselib.utils.DensityUtils
|
import com.tenlionsoft.baselib.utils.DensityUtils
|
||||||
import com.tenlionsoft.baselib.utils.SpUtils
|
import com.tenlionsoft.baselib.utils.SpUtils
|
||||||
@ -27,6 +31,8 @@ import com.tenlionsoft.baselib.widget.AdapterItemClickListener
|
|||||||
import com.tenlionsoft.baselib.widget.LoadingDialog
|
import com.tenlionsoft.baselib.widget.LoadingDialog
|
||||||
import com.tenlionsoft.baselib.widget.SoftKeyBoardListener
|
import com.tenlionsoft.baselib.widget.SoftKeyBoardListener
|
||||||
import com.tenlionsoft.baselib.widget.wheel.WheelView
|
import com.tenlionsoft.baselib.widget.wheel.WheelView
|
||||||
|
import com.tenlionsoft.medialib.base.SimplePhotoActivity
|
||||||
|
import com.tenlionsoft.medialib.base.SimpleVideoActivity
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,11 +111,8 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
|
|||||||
chatPageViewModel!!.showLoadDialog.observe(this) {
|
chatPageViewModel!!.showLoadDialog.observe(this) {
|
||||||
if (it) {
|
if (it) {
|
||||||
//显示loading
|
//显示loading
|
||||||
mLoading = LoadingDialog.Builder(this)
|
mLoading = LoadingDialog.Builder(this).setCancelOutside(false).setCancelable(false)
|
||||||
.setCancelOutside(false)
|
.setMessage("上传中...").create()
|
||||||
.setCancelable(false)
|
|
||||||
.setMessage("上传中...")
|
|
||||||
.create()
|
|
||||||
mLoading!!.show()
|
mLoading!!.show()
|
||||||
} else {
|
} else {
|
||||||
//隐藏loading
|
//隐藏loading
|
||||||
@ -163,8 +166,7 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
|
|||||||
|
|
||||||
mBinding.rlvChats.postDelayed({
|
mBinding.rlvChats.postDelayed({
|
||||||
layoutManager.scrollToPositionWithOffset(
|
layoutManager.scrollToPositionWithOffset(
|
||||||
itemCount - 1,
|
itemCount - 1, 0
|
||||||
0
|
|
||||||
)
|
)
|
||||||
}, 50)
|
}, 50)
|
||||||
chatPageViewModel!!.scrollListToBottom.value = false
|
chatPageViewModel!!.scrollListToBottom.value = false
|
||||||
@ -182,38 +184,35 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
|
|||||||
bottomHeight = mBinding.llBottomLayout.height
|
bottomHeight = mBinding.llBottomLayout.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SoftKeyBoardListener().setChangeListener(this@ChatActivity,
|
SoftKeyBoardListener().setChangeListener(this@ChatActivity, { h ->
|
||||||
{ h ->
|
Log.e("ChatActivity", "软键盘: 显示")
|
||||||
Log.e("ChatActivity", "软键盘: 显示")
|
chatPageViewModel!!.showReplyLayout.value = false
|
||||||
chatPageViewModel!!.showReplyLayout.value = false
|
chatPageViewModel!!.showEmojiLayout.value = false
|
||||||
chatPageViewModel!!.showEmojiLayout.value = false
|
chatPageViewModel!!.showChooseLayout.value = false
|
||||||
chatPageViewModel!!.showChooseLayout.value = false
|
mBinding.rlBottom.visibility = View.VISIBLE
|
||||||
mBinding.rlBottom.visibility = View.VISIBLE
|
//重置layout高度
|
||||||
//重置layout高度
|
val layoutParams = mBinding.rlvChats.layoutParams
|
||||||
|
layoutParams.height = mContentHeight - h
|
||||||
|
mBinding.rlvChats.layoutParams = layoutParams
|
||||||
|
val layoutManager = mBinding.rlvChats.layoutManager as LinearLayoutManager
|
||||||
|
val itemCount = layoutManager.itemCount
|
||||||
|
//滚动到底部
|
||||||
|
mBinding.rlvChats.postDelayed({
|
||||||
|
layoutManager.scrollToPositionWithOffset(
|
||||||
|
itemCount - 1, 0
|
||||||
|
)
|
||||||
|
}, 100)
|
||||||
|
}, { _ ->
|
||||||
|
Log.e("ChatActivity", "软键盘: 隐藏")
|
||||||
|
val isShowOther =
|
||||||
|
chatPageViewModel!!.showReplyLayout.value!! || chatPageViewModel!!.showEmojiLayout.value!! || chatPageViewModel!!.showChooseLayout.value!!
|
||||||
|
if (!isShowOther) {
|
||||||
val layoutParams = mBinding.rlvChats.layoutParams
|
val layoutParams = mBinding.rlvChats.layoutParams
|
||||||
layoutParams.height = mContentHeight - h
|
layoutParams.height = mContentHeight
|
||||||
mBinding.rlvChats.layoutParams = layoutParams
|
mBinding.rlvChats.layoutParams = layoutParams
|
||||||
val layoutManager = mBinding.rlvChats.layoutManager as LinearLayoutManager
|
mBinding.rlBottom.visibility = View.GONE
|
||||||
val itemCount = layoutManager.itemCount
|
}
|
||||||
//滚动到底部
|
})
|
||||||
mBinding.rlvChats.postDelayed({
|
|
||||||
layoutManager.scrollToPositionWithOffset(
|
|
||||||
itemCount - 1,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
}, 100)
|
|
||||||
},
|
|
||||||
{ _ ->
|
|
||||||
Log.e("ChatActivity", "软键盘: 隐藏")
|
|
||||||
val isShowOther =
|
|
||||||
chatPageViewModel!!.showReplyLayout.value!! || chatPageViewModel!!.showEmojiLayout.value!! || chatPageViewModel!!.showChooseLayout.value!!
|
|
||||||
if (!isShowOther) {
|
|
||||||
val layoutParams = mBinding.rlvChats.layoutParams
|
|
||||||
layoutParams.height = mContentHeight
|
|
||||||
mBinding.rlvChats.layoutParams = layoutParams
|
|
||||||
mBinding.rlBottom.visibility = View.GONE
|
|
||||||
}
|
|
||||||
})
|
|
||||||
registerLocalReceiver()
|
registerLocalReceiver()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,8 +241,7 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
|
|||||||
//滚动到底部
|
//滚动到底部
|
||||||
mBinding.rlvChats.postDelayed({
|
mBinding.rlvChats.postDelayed({
|
||||||
layoutManager.scrollToPositionWithOffset(
|
layoutManager.scrollToPositionWithOffset(
|
||||||
itemCount - 1,
|
itemCount - 1, 0
|
||||||
0
|
|
||||||
)
|
)
|
||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
@ -274,6 +272,34 @@ class ChatActivity : BaseActivity(), AdapterItemClickListener<MsgBean> {
|
|||||||
|
|
||||||
//条目点击
|
//条目点击
|
||||||
override fun onItemClick(data: MsgBean) {
|
override fun onItemClick(data: MsgBean) {
|
||||||
|
when (data.messageType) {
|
||||||
|
ProjectConfig.MSG_TEXT -> {}
|
||||||
|
ProjectConfig.MSG_IMG -> {
|
||||||
|
val intent = Intent(this@ChatActivity, SimplePhotoActivity::class.java)
|
||||||
|
val gson = Gson()
|
||||||
|
val body = gson.fromJson(data.body, BodyContent::class.java)
|
||||||
|
val fileDataBean = gson.fromJson(body.content, FileDataBean::class.java)
|
||||||
|
val url = NetConfig.MAIN_URL + fileDataBean.fileUrl
|
||||||
|
val urls = arrayListOf(url)
|
||||||
|
intent.putStringArrayListExtra(SimplePhotoActivity.TAG_IMGURL, urls)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectConfig.MSG_FILE -> {
|
||||||
|
//TODO 下载文集,调用系统打开
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectConfig.MSG_VIDEO -> {
|
||||||
|
val intent = Intent(this@ChatActivity, SimpleVideoActivity::class.java)
|
||||||
|
val gson = Gson()
|
||||||
|
val body = gson.fromJson(data.body, BodyContent::class.java)
|
||||||
|
val fileDataBean = gson.fromJson(body.content, FileDataBean::class.java)
|
||||||
|
intent.putExtra("url", fileDataBean.fileUrl)
|
||||||
|
intent.putExtra("title", fileDataBean.fileName)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.tenlionsoft.aimz_k.page.fragments
|
package com.tenlionsoft.aimz_k.page.fragments
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -8,7 +9,8 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.tenlionsoft.aimz_k.R
|
import com.tenlionsoft.aimz_k.R
|
||||||
import com.tenlionsoft.aimz_k.databinding.FragmentMsgBinding
|
import com.tenlionsoft.aimz_k.databinding.FragmentMsgBinding
|
||||||
import com.tenlionsoft.aimz_k.model.MsgCategoryBean
|
import com.tenlionsoft.aimz_k.model.MsgCategoryBean
|
||||||
@ -18,24 +20,21 @@ import com.tenlionsoft.aimz_k.services.SocketService
|
|||||||
import com.tenlionsoft.aimz_k.viewmodel.MsgViewModel
|
import com.tenlionsoft.aimz_k.viewmodel.MsgViewModel
|
||||||
import com.tenlionsoft.baselib.contacts.ProjectConfig
|
import com.tenlionsoft.baselib.contacts.ProjectConfig
|
||||||
import com.tenlionsoft.baselib.widget.AdapterItemClickListener
|
import com.tenlionsoft.baselib.widget.AdapterItemClickListener
|
||||||
|
import com.tenlionsoft.baselib.widget.AdapterItemLongClickListener
|
||||||
import com.tenlionsoft.baselib.widget.LoadingDialog
|
import com.tenlionsoft.baselib.widget.LoadingDialog
|
||||||
|
|
||||||
class MsgFragment : Fragment(), AdapterItemClickListener<MsgCategoryBean>,
|
class MsgFragment : Fragment(), AdapterItemClickListener<MsgCategoryBean>,
|
||||||
MainActivity.OnSocketConnectListener {
|
MainActivity.OnSocketConnectListener, AdapterItemLongClickListener<MsgCategoryBean> {
|
||||||
private lateinit var mMsgBinding: FragmentMsgBinding
|
private lateinit var mMsgBinding: FragmentMsgBinding
|
||||||
private var mActivity: MainActivity? = null
|
private var mActivity: MainActivity? = null
|
||||||
private var mLoading: LoadingDialog? = null;
|
private var mLoading: LoadingDialog? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = MsgFragment()
|
fun newInstance() = MsgFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val viewModel: MsgViewModel by viewModels()
|
private lateinit var viewModel: MsgViewModel
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
@ -46,15 +45,21 @@ class MsgFragment : Fragment(), AdapterItemClickListener<MsgCategoryBean>,
|
|||||||
R.layout.fragment_msg,
|
R.layout.fragment_msg,
|
||||||
container,
|
container,
|
||||||
false
|
false
|
||||||
);
|
)
|
||||||
mMsgBinding.llSearchLayout.llSearchLayout.setOnClickListener {
|
mMsgBinding.llSearchLayout.llSearchLayout.setOnClickListener {
|
||||||
startActivity(Intent(this@MsgFragment.context, ChatActivity::class.java))
|
startActivity(Intent(this@MsgFragment.context, ChatActivity::class.java))
|
||||||
}
|
}
|
||||||
|
viewModel = ViewModelProvider(this, object : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return MsgViewModel() as T
|
||||||
|
}
|
||||||
|
})[MsgViewModel::class.java]
|
||||||
mMsgBinding.msgViewModel = viewModel
|
mMsgBinding.msgViewModel = viewModel
|
||||||
mMsgBinding.lifecycleOwner = this
|
mMsgBinding.lifecycleOwner = this
|
||||||
viewModel.onItemClickListener = this//条目点击
|
viewModel.onItemClickListener = this//条目点击
|
||||||
initView();
|
viewModel.onItemLongClickListener = this//长按
|
||||||
return mMsgBinding.root;
|
initView()
|
||||||
|
return mMsgBinding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,6 +84,19 @@ class MsgFragment : Fragment(), AdapterItemClickListener<MsgCategoryBean>,
|
|||||||
mMsgBinding.llSearchLayout.pbSocketLoading.visibility = View.GONE
|
mMsgBinding.llSearchLayout.pbSocketLoading.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModel.showLoadDialog.observe(viewLifecycleOwner) {
|
||||||
|
if (it) {
|
||||||
|
//显示loading
|
||||||
|
mLoading = LoadingDialog.Builder(mActivity as Activity).setCancelOutside(false)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setMessage("删除中...").create()
|
||||||
|
mLoading!!.show()
|
||||||
|
} else {
|
||||||
|
//隐藏loading
|
||||||
|
mLoading?.dismiss()
|
||||||
|
mLoading = null
|
||||||
|
}
|
||||||
|
}
|
||||||
//登陆socket
|
//登陆socket
|
||||||
viewModel.doLoginWebsocket()
|
viewModel.doLoginWebsocket()
|
||||||
}
|
}
|
||||||
@ -104,11 +122,11 @@ class MsgFragment : Fragment(), AdapterItemClickListener<MsgCategoryBean>,
|
|||||||
/**
|
/**
|
||||||
* 条目点击
|
* 条目点击
|
||||||
*/
|
*/
|
||||||
override fun onItemClick(msgCategoryBean: MsgCategoryBean) {
|
override fun onItemClick(data: MsgCategoryBean) {
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent(mActivity, ChatActivity::class.java).putExtra(
|
Intent(mActivity, ChatActivity::class.java).putExtra(
|
||||||
"fromId",
|
"fromId",
|
||||||
msgCategoryBean.senderId
|
data.senderId
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -133,4 +151,10 @@ class MsgFragment : Fragment(), AdapterItemClickListener<MsgCategoryBean>,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//长按
|
||||||
|
override fun onItemLongClick(type: Int, d: MsgCategoryBean) {
|
||||||
|
//删除聊天
|
||||||
|
viewModel.doDelChat(d)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.media.MediaPlayer
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@ -26,6 +27,7 @@ class SocketService : Service(), WsManager.MsgCallBack {
|
|||||||
private var mWsManager: WsManager? = null
|
private var mWsManager: WsManager? = null
|
||||||
private val gson = Gson()
|
private val gson = Gson()
|
||||||
private lateinit var mLocalReceiver: LocalReceiver
|
private lateinit var mLocalReceiver: LocalReceiver
|
||||||
|
private lateinit var mMediaPlayer: MediaPlayer
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
@ -54,6 +56,7 @@ class SocketService : Service(), WsManager.MsgCallBack {
|
|||||||
// startForeground(startId, notification)
|
// startForeground(startId, notification)
|
||||||
|
|
||||||
this.startSocket();//开启Socket
|
this.startSocket();//开启Socket
|
||||||
|
mMediaPlayer = MediaPlayer.create(this, com.tenlionsoft.baselib.R.raw.chat)
|
||||||
registerLocalReceiver()
|
registerLocalReceiver()
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
}
|
}
|
||||||
@ -98,6 +101,7 @@ class SocketService : Service(), WsManager.MsgCallBack {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
stopSocket();
|
stopSocket();
|
||||||
unregisterReceiver(mLocalReceiver)
|
unregisterReceiver(mLocalReceiver)
|
||||||
|
mMediaPlayer.release()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,9 +127,12 @@ class SocketService : Service(), WsManager.MsgCallBack {
|
|||||||
|
|
||||||
//socket接收到信息
|
//socket接收到信息
|
||||||
override fun onCallBackMsg(str: String) {
|
override fun onCallBackMsg(str: String) {
|
||||||
Log.e("SocketService", "接收信息: ${str}")
|
Log.e("SocketService", "接收信息:${str}")
|
||||||
val intent = Intent(ProjectConfig.A_S_MSG_RECEIVER)
|
val intent = Intent(ProjectConfig.A_S_MSG_RECEIVER)
|
||||||
intent.putExtra("msg", str)
|
intent.putExtra("msg", str)
|
||||||
sendBroadcast(intent)
|
sendBroadcast(intent)
|
||||||
|
if (!str.contains("STATUS")) {
|
||||||
|
mMediaPlayer.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -43,7 +43,7 @@ import okhttp3.RequestBody.Companion.asRequestBody
|
|||||||
class ChatPageViewModel(
|
class ChatPageViewModel(
|
||||||
private val fromId: String,
|
private val fromId: String,
|
||||||
private val toId: String,
|
private val toId: String,
|
||||||
private val context: Context
|
private var context: Context
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
val txtMsg = MutableLiveData<String>("")
|
val txtMsg = MutableLiveData<String>("")
|
||||||
val showSendBtn = MutableLiveData(false)//显示/隐藏发送按钮
|
val showSendBtn = MutableLiveData(false)//显示/隐藏发送按钮
|
||||||
@ -284,6 +284,7 @@ class ChatPageViewModel(
|
|||||||
_msgList.value = _msgList.value?.plus(msgBean)
|
_msgList.value = _msgList.value?.plus(msgBean)
|
||||||
adapter.setData(_msgList.value!!)
|
adapter.setData(_msgList.value!!)
|
||||||
}
|
}
|
||||||
|
sendHandlerToLooper(b)
|
||||||
intent.putExtra("msgBean", b)
|
intent.putExtra("msgBean", b)
|
||||||
context.sendBroadcast(intent)
|
context.sendBroadcast(intent)
|
||||||
scrollListToBottom.value = true
|
scrollListToBottom.value = true
|
||||||
|
@ -4,7 +4,6 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.tenlionsoft.aimz_k.ConvertBeanUtils
|
import com.tenlionsoft.aimz_k.ConvertBeanUtils
|
||||||
@ -16,18 +15,22 @@ import com.tenlionsoft.aimz_k.model.MsgCategoryBean
|
|||||||
import com.tenlionsoft.aimz_k.model.MsgConvertBean
|
import com.tenlionsoft.aimz_k.model.MsgConvertBean
|
||||||
import com.tenlionsoft.aimz_k.model.Receiver
|
import com.tenlionsoft.aimz_k.model.Receiver
|
||||||
import com.tenlionsoft.aimz_k.model.Sender
|
import com.tenlionsoft.aimz_k.model.Sender
|
||||||
|
import com.tenlionsoft.baselib.base.BaseViewModel
|
||||||
import com.tenlionsoft.baselib.contacts.ProjectConfig
|
import com.tenlionsoft.baselib.contacts.ProjectConfig
|
||||||
import com.tenlionsoft.baselib.utils.SpUtils
|
import com.tenlionsoft.baselib.utils.SpUtils
|
||||||
import com.tenlionsoft.baselib.utils.TimeUtils
|
import com.tenlionsoft.baselib.utils.TimeUtils
|
||||||
|
import com.tenlionsoft.baselib.utils.ToastUtils
|
||||||
import com.tenlionsoft.baselib.widget.AdapterItemClickListener
|
import com.tenlionsoft.baselib.widget.AdapterItemClickListener
|
||||||
|
import com.tenlionsoft.baselib.widget.AdapterItemLongClickListener
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class MsgViewModel : ViewModel() {
|
class MsgViewModel : BaseViewModel() {
|
||||||
private val _pwdList = MutableLiveData<List<MsgCategoryBean>>()
|
private val _pwdList = MutableLiveData<List<MsgCategoryBean>>()
|
||||||
var adapter: CategoryAdapter = CategoryAdapter(_pwdList.value ?: emptyList(), this)
|
var adapter: CategoryAdapter = CategoryAdapter(_pwdList.value ?: emptyList(), this)
|
||||||
var onItemClickListener: AdapterItemClickListener<MsgCategoryBean>? = null
|
var onItemClickListener: AdapterItemClickListener<MsgCategoryBean>? = null
|
||||||
|
var onItemLongClickListener: AdapterItemLongClickListener<MsgCategoryBean>? = null
|
||||||
val isLogining = MutableLiveData(false)
|
val isLogining = MutableLiveData(false)
|
||||||
private val mGson = Gson()
|
private val mGson = Gson()
|
||||||
private var mCurrentPage: Int = 1
|
private var mCurrentPage: Int = 1
|
||||||
@ -157,4 +160,20 @@ class MsgViewModel : ViewModel() {
|
|||||||
fun refresh() {
|
fun refresh() {
|
||||||
getList(true)
|
getList(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//删除聊天
|
||||||
|
fun doDelChat(d: MsgCategoryBean) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
showLoadDialog.value = true
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val cDao = DbManager.db.categoryDao()
|
||||||
|
val mDao = DbManager.db.msgDao()
|
||||||
|
cDao.delChatBySenderId(d.senderId!!)
|
||||||
|
mDao.delChatHistory(d.senderId!!, d.receiverId!!)
|
||||||
|
}
|
||||||
|
showLoadDialog.value = false
|
||||||
|
ToastUtils.success("删除成功")
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -2,4 +2,16 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Theme.Aimz_k" parent="Theme.AppCompat.Light.NoActionBar" />
|
<style name="Theme.Aimz_k" parent="Theme.AppCompat.Light.NoActionBar" />
|
||||||
|
|
||||||
|
<style name="Anim_fade" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
|
<item name="android:windowEnterAnimation">@anim/fade_in</item>
|
||||||
|
<item name="android:windowExitAnimation">@anim/fade_out</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="fade" parent="@android:style/Animation.Activity">
|
||||||
|
<item name="android:activityOpenEnterAnimation">@anim/fade_in</item>
|
||||||
|
<item name="android:activityOpenExitAnimation">@anim/fade_out</item>
|
||||||
|
<item name="android:activityCloseEnterAnimation">@anim/fade_in</item>
|
||||||
|
<item name="android:activityCloseExitAnimation">@anim/fade_out</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
@ -44,7 +44,6 @@ dependencies {
|
|||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|
||||||
api(libs.rxjava)
|
api(libs.rxjava)
|
||||||
api(libs.rxandroid)
|
api(libs.rxandroid)
|
||||||
implementation(libs.mmkv)
|
implementation(libs.mmkv)
|
||||||
@ -67,4 +66,5 @@ dependencies {
|
|||||||
api(libs.xxpermissions)
|
api(libs.xxpermissions)
|
||||||
api(libs.filepicker)
|
api(libs.filepicker)
|
||||||
api(libs.room.ktx)
|
api(libs.room.ktx)
|
||||||
|
api(libs.popup)
|
||||||
}
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.tenlionsoft.baselib.widget
|
||||||
|
|
||||||
|
interface AdapterItemLongClickListener<D> {
|
||||||
|
fun onItemLongClick(type: Int, d: D)
|
||||||
|
}
|
6
baselib/src/main/res/anim/fade_in.xml
Normal file
6
baselib/src/main/res/anim/fade_in.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="0.0"
|
||||||
|
android:interpolator="@android:anim/accelerate_interpolator"
|
||||||
|
android:toAlpha="1.0" />
|
6
baselib/src/main/res/anim/fade_out.xml
Normal file
6
baselib/src/main/res/anim/fade_out.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:interpolator="@android:anim/accelerate_interpolator"
|
||||||
|
android:toAlpha="0.0" />
|
7
baselib/src/main/res/anim/slide_in_right.xml
Normal file
7
baselib/src/main/res/anim/slide_in_right.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate
|
||||||
|
android:fromXDelta="100%p"
|
||||||
|
android:toXDelta="0"
|
||||||
|
android:duration="500" />
|
||||||
|
</set>
|
7
baselib/src/main/res/anim/slide_out_left.xml
Normal file
7
baselib/src/main/res/anim/slide_out_left.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate
|
||||||
|
android:fromXDelta="0"
|
||||||
|
android:toXDelta="-100%p"
|
||||||
|
android:duration="500" />
|
||||||
|
</set>
|
BIN
baselib/src/main/res/drawable-xhdpi/ic_del.png
Normal file
BIN
baselib/src/main/res/drawable-xhdpi/ic_del.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 808 B |
BIN
baselib/src/main/res/drawable-xhdpi/ic_img_loading.png
Normal file
BIN
baselib/src/main/res/drawable-xhdpi/ic_img_loading.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
@ -1,32 +1,32 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="160dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="100dp"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/shp_tr_radius_5"
|
android:background="@drawable/shp_tr_radius_5"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="160dp"
|
android:layout_width="120dp"
|
||||||
android:layout_height="100dp"
|
android:layout_height="80dp"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/pb_state_loading"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:indeterminateBehavior="repeat"
|
|
||||||
android:indeterminateDrawable="@drawable/anim_loading" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tipTextView"
|
android:id="@+id/tipTextView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/pb_state_loading"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:text="处理中..."
|
android:text="处理中..."
|
||||||
android:textColor="@color/black" />
|
android:textColor="@color/black" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/pb_state_loading"
|
||||||
|
android:layout_width="35dp"
|
||||||
|
android:layout_height="35dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:indeterminateBehavior="repeat"
|
||||||
|
android:indeterminateDrawable="@drawable/anim_loading" />
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
BIN
baselib/src/main/res/raw/chat.mp3
Normal file
BIN
baselib/src/main/res/raw/chat.mp3
Normal file
Binary file not shown.
@ -37,8 +37,8 @@ emoji2Emojipicker = "1.0.0-alpha03"
|
|||||||
xxpermissions = "20.0"
|
xxpermissions = "20.0"
|
||||||
filepicker = "2.0.0"
|
filepicker = "2.0.0"
|
||||||
room-ktx = "2.2.5"
|
room-ktx = "2.2.5"
|
||||||
|
ijkplayer = "v10.0.0"
|
||||||
|
popup = "2.10.0"
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
@ -87,9 +87,15 @@ emojipicker = { group = "androidx.emoji2", name = "emoji2-emojipicker", version.
|
|||||||
xxpermissions = { group = "com.github.getActivity", name = "XXPermissions", version.ref = "xxpermissions" }
|
xxpermissions = { group = "com.github.getActivity", name = "XXPermissions", version.ref = "xxpermissions" }
|
||||||
filepicker = { group = "com.github.atwa", name = "filepicker", version.ref = "filepicker" }
|
filepicker = { group = "com.github.atwa", name = "filepicker", version.ref = "filepicker" }
|
||||||
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room-ktx" }
|
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room-ktx" }
|
||||||
|
ijkplayer = { group = "com.github.CarGuo.GSYVideoPlayer", name = "gsyvideoplayer-java", version.ref = "ijkplayer" }
|
||||||
|
#ijk模式的so
|
||||||
|
ijkplayer-arm64 = { group = "com.github.CarGuo.GSYVideoPlayer", name = "gsyvideoplayer-arm64", version.ref = "ijkplayer" }
|
||||||
|
ijkplayer-armv7a = { group = "com.github.CarGuo.GSYVideoPlayer", name = "gsyvideoplayer-armv7a", version.ref = "ijkplayer" }
|
||||||
|
# popup
|
||||||
|
popup = { group = "com.github.li-xiaojun", name = "XPopup", version.ref = "popup" }
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version = "2.0.21" }
|
||||||
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kapt" }
|
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kapt" }
|
||||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||||
|
|
||||||
|
1
medialib/.gitignore
vendored
Normal file
1
medialib/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
60
medialib/build.gradle.kts
Normal file
60
medialib/build.gradle.kts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.library)
|
||||||
|
alias(libs.plugins.kotlin.android)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.tenlionsoft.medialib"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 24
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
|
ndk {
|
||||||
|
abiFilters.addAll(arrayOf("armeabi", "armeabi-v7a", "x86", "x86_64", "arm64-v8a"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding = true
|
||||||
|
dataBinding = true
|
||||||
|
}
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion = "1.5.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
implementation(libs.androidx.appcompat)
|
||||||
|
implementation(libs.material)
|
||||||
|
implementation(libs.androidx.activity)
|
||||||
|
implementation(libs.androidx.constraintlayout)
|
||||||
|
implementation(libs.androidx.core.ktx)
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
api(libs.ijkplayer)
|
||||||
|
api(libs.ijkplayer.arm64)
|
||||||
|
api(libs.ijkplayer.armv7a)
|
||||||
|
api(project(":baselib"))
|
||||||
|
|
||||||
|
}
|
0
medialib/consumer-rules.pro
Normal file
0
medialib/consumer-rules.pro
Normal file
21
medialib/proguard-rules.pro
vendored
Normal file
21
medialib/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.tenlionsoft.medialib;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
|
assertEquals("com.tenlionsoft.medialib.test", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
15
medialib/src/main/AndroidManifest.xml
Normal file
15
medialib/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<activity
|
||||||
|
android:name=".base.SimpleVideoActivity"
|
||||||
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".base.SimplePhotoActivity"
|
||||||
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
|
android:exported="false" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
@ -0,0 +1,61 @@
|
|||||||
|
package com.tenlionsoft.medialib.base
|
||||||
|
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.viewpager.widget.ViewPager.OnPageChangeListener
|
||||||
|
import com.tenlionsoft.baselib.base.BaseActivity
|
||||||
|
import com.tenlionsoft.baselib.utils.ToastUtils
|
||||||
|
import com.tenlionsoft.medialib.R
|
||||||
|
import com.tenlionsoft.medialib.base.adapter.PhotoAdapter
|
||||||
|
import com.tenlionsoft.medialib.databinding.ActivitySimplePhotoBinding
|
||||||
|
|
||||||
|
class SimplePhotoActivity : BaseActivity() {
|
||||||
|
private lateinit var mBind: ActivitySimplePhotoBinding
|
||||||
|
private var mImgUrls: java.util.ArrayList<String>? = null
|
||||||
|
override fun bindView() {
|
||||||
|
mBind = DataBindingUtil.setContentView(
|
||||||
|
this@SimplePhotoActivity, R.layout.activity_simple_photo
|
||||||
|
)
|
||||||
|
mImgUrls = intent.getStringArrayListExtra(TAG_IMGURL)
|
||||||
|
val curItem = intent.getIntExtra("curItem", 0)
|
||||||
|
if (mImgUrls == null) {
|
||||||
|
ToastUtils.error("图片链接地址有误")
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
val imageAdapter: PhotoAdapter = PhotoAdapter(this, mImgUrls!!)
|
||||||
|
mBind.vpImages.setAdapter(imageAdapter)
|
||||||
|
imageAdapter.addImgClick(object : PhotoAdapter.OnImgClick {
|
||||||
|
override fun imgClick() {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mBind.tvCount.text =
|
||||||
|
String.format(
|
||||||
|
resources.getString(R.string.img_position), (curItem + 1), mImgUrls!!.size
|
||||||
|
)
|
||||||
|
|
||||||
|
mBind.vpImages.setCurrentItem(curItem)
|
||||||
|
|
||||||
|
mBind.vpImages.addOnPageChangeListener(object : OnPageChangeListener {
|
||||||
|
override fun onPageScrolled(
|
||||||
|
position: Int, positionOffset: Float, positionOffsetPixels: Int
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageSelected(position: Int) {
|
||||||
|
var pos = position
|
||||||
|
mBind.tvCount.text = String.format(
|
||||||
|
resources.getString(R.string.img_position), ++pos, mImgUrls!!.size
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageScrollStateChanged(state: Int) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG_IMGURL: String = "imgUrls"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
package com.tenlionsoft.medialib.base
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import com.shuyu.gsyvideoplayer.GSYVideoManager
|
||||||
|
import com.shuyu.gsyvideoplayer.utils.OrientationUtils
|
||||||
|
import com.tenlionsoft.baselib.base.BaseActivity
|
||||||
|
import com.tenlionsoft.baselib.contacts.NetConfig
|
||||||
|
import com.tenlionsoft.baselib.utils.ToastUtils
|
||||||
|
import com.tenlionsoft.medialib.databinding.ActivitySimpleVideoBinding
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleVideoActivity : BaseActivity() {
|
||||||
|
private lateinit var mBind: ActivitySimpleVideoBinding
|
||||||
|
private lateinit var orientationUtils: OrientationUtils
|
||||||
|
override fun bindView() {
|
||||||
|
mBind = DataBindingUtil.setContentView(
|
||||||
|
this@SimpleVideoActivity, com.tenlionsoft.medialib.R.layout.activity_simple_video
|
||||||
|
)
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
|
window.statusBarColor = ContextCompat.getColor(this, com.tenlionsoft.baselib.R.color.black)
|
||||||
|
val source = intent.getStringExtra("url")
|
||||||
|
val title = intent.getStringExtra("title") ?: "视频预览"
|
||||||
|
if (source.isNullOrEmpty()) {
|
||||||
|
ToastUtils.error("视频地址有误")
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mBind.sVideo.setUp(NetConfig.MAIN_URL + source, true, title)
|
||||||
|
|
||||||
|
|
||||||
|
//增加封面
|
||||||
|
// val imageView = ImageView(this)
|
||||||
|
// imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
|
// imageView.setImageResource(com.tenlionsoft.medialib.R.mipmap.xxx1)
|
||||||
|
// videoPlayer.setThumbImageView(imageView)
|
||||||
|
|
||||||
|
//增加title
|
||||||
|
mBind.sVideo.titleTextView.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
//设置返回键
|
||||||
|
mBind.sVideo.backButton.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
//设置旋转
|
||||||
|
orientationUtils = OrientationUtils(this, mBind.sVideo)
|
||||||
|
|
||||||
|
//设置全屏按键功能,这是使用的是选择屏幕,而不是全屏
|
||||||
|
mBind.sVideo.getFullscreenButton().setOnClickListener(View.OnClickListener {
|
||||||
|
// ------- !!!如果不需要旋转屏幕,可以不调用!!!-------
|
||||||
|
// 不需要屏幕旋转,还需要设置 setNeedOrientationUtils(false)
|
||||||
|
orientationUtils.resolveByClick()
|
||||||
|
//finish();
|
||||||
|
})
|
||||||
|
|
||||||
|
//是否可以滑动调整
|
||||||
|
mBind.sVideo.setIsTouchWiget(true)
|
||||||
|
|
||||||
|
//设置返回按键功能
|
||||||
|
mBind.sVideo.getBackButton().setOnClickListener(View.OnClickListener { onBackPressed() })
|
||||||
|
|
||||||
|
|
||||||
|
/**不需要屏幕旋转 */
|
||||||
|
mBind.sVideo.setNeedOrientationUtils(false)
|
||||||
|
|
||||||
|
mBind.sVideo.startPlayLogic()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
mBind.sVideo.onVideoResume()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
mBind.sVideo.onVideoPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
/** 不需要回归竖屏 */
|
||||||
|
// if (orientationUtils.getScreenType() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
|
||||||
|
// videoPlayer.getFullscreenButton().performClick();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//释放所有
|
||||||
|
mBind.sVideo.setVideoAllCallBack(null)
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
GSYVideoManager.releaseAllVideos()
|
||||||
|
orientationUtils.releaseListener()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.viewpager.widget.PagerAdapter
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import com.tenlionsoft.medialib.R
|
||||||
|
import com.tenlionsoft.medialib.base.widget.PhotoView
|
||||||
|
|
||||||
|
class PhotoAdapter(private val ctx: Context, private val imgs: MutableList<String>) :
|
||||||
|
PagerAdapter() {
|
||||||
|
|
||||||
|
private var mOnImgClick: OnImgClick? = null
|
||||||
|
override fun instantiateItem(container: ViewGroup, position: Int): Any {
|
||||||
|
val view: View = LayoutInflater.from(ctx)
|
||||||
|
.inflate(R.layout.item_photo, container, false)
|
||||||
|
val photoView = view.findViewById<PhotoView>(R.id.pv_view)
|
||||||
|
val options: RequestOptions =
|
||||||
|
RequestOptions().error(com.tenlionsoft.baselib.R.drawable.ic_img_load_err)
|
||||||
|
.placeholder(com.tenlionsoft.baselib.R.drawable.ic_img_loading)
|
||||||
|
Glide.with(ctx)
|
||||||
|
.load(imgs.get(position))
|
||||||
|
.apply(options)
|
||||||
|
.into(photoView)
|
||||||
|
container.addView(view)
|
||||||
|
if (mOnImgClick != null) {
|
||||||
|
photoView.setOnClickListener { _ -> mOnImgClick?.imgClick() }
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun addImgClick(onImgClick: OnImgClick?) {
|
||||||
|
this.mOnImgClick = onImgClick
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnImgClick {
|
||||||
|
fun imgClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return imgs.size
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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`
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
class Compat {
|
||||||
|
companion object {
|
||||||
|
const val SIXTY_FPS_INTERVAL: Int = 1000 / 60
|
||||||
|
|
||||||
|
fun postOnAnimation(view: View, runnable: Runnable) {
|
||||||
|
postOnAnimationJellyBean(view, runnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postOnAnimationJellyBean(view: View, runnable: Runnable) {
|
||||||
|
view.postOnAnimation(runnable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,193 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.ScaleGestureDetector
|
||||||
|
import android.view.ScaleGestureDetector.OnScaleGestureListener
|
||||||
|
import android.view.VelocityTracker
|
||||||
|
import android.view.ViewConfiguration
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
class CustomGestureDetector(private val context: Context, listener: OnGestureListener) {
|
||||||
|
val INVALID_POINTER_ID: Int = -1
|
||||||
|
|
||||||
|
private var mActivePointerId = INVALID_POINTER_ID
|
||||||
|
private var mActivePointerIndex = 0
|
||||||
|
private var mDetector: ScaleGestureDetector? = null
|
||||||
|
|
||||||
|
private var mVelocityTracker: VelocityTracker? = null
|
||||||
|
private var mIsDragging = false
|
||||||
|
private var mLastTouchX = 0f
|
||||||
|
private var mLastTouchY = 0f
|
||||||
|
private var mTouchSlop = 0f
|
||||||
|
private var mMinimumVelocity = 0f
|
||||||
|
private var mListener: OnGestureListener? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
val configuration = ViewConfiguration
|
||||||
|
.get(context)
|
||||||
|
mMinimumVelocity = configuration.scaledMinimumFlingVelocity.toFloat()
|
||||||
|
mTouchSlop = configuration.scaledTouchSlop.toFloat()
|
||||||
|
|
||||||
|
mListener = listener
|
||||||
|
val mScaleListener: OnScaleGestureListener = object : OnScaleGestureListener {
|
||||||
|
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
||||||
|
val scaleFactor = detector.scaleFactor
|
||||||
|
|
||||||
|
if (java.lang.Float.isNaN(scaleFactor) || java.lang.Float.isInfinite(scaleFactor)) return false
|
||||||
|
|
||||||
|
mListener?.onScale(
|
||||||
|
scaleFactor,
|
||||||
|
detector.focusX, detector.focusY
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScaleEnd(detector: ScaleGestureDetector) {
|
||||||
|
// NO-OP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mDetector = ScaleGestureDetector(context, mScaleListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getActiveX(ev: MotionEvent): Float {
|
||||||
|
return try {
|
||||||
|
ev.getX(mActivePointerIndex)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ev.x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getActiveY(ev: MotionEvent): Float {
|
||||||
|
return try {
|
||||||
|
ev.getY(mActivePointerIndex)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ev.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isScaling(): Boolean {
|
||||||
|
return mDetector!!.isInProgress
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isDragging(): Boolean {
|
||||||
|
return mIsDragging
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
try {
|
||||||
|
mDetector!!.onTouchEvent(ev)
|
||||||
|
return processTouchEvent(ev)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
// Fix for support lib bug, happening when onDestroy is called
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
val action = ev.action
|
||||||
|
when (action and MotionEvent.ACTION_MASK) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
mActivePointerId = ev.getPointerId(0)
|
||||||
|
|
||||||
|
mVelocityTracker = VelocityTracker.obtain()
|
||||||
|
mVelocityTracker?.addMovement(ev)
|
||||||
|
|
||||||
|
mLastTouchX = getActiveX(ev)
|
||||||
|
mLastTouchY = getActiveY(ev)
|
||||||
|
mIsDragging = false
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
val x = getActiveX(ev)
|
||||||
|
val y = getActiveY(ev)
|
||||||
|
val dx = x - mLastTouchX
|
||||||
|
val dy = y - mLastTouchY
|
||||||
|
|
||||||
|
if (!mIsDragging) {
|
||||||
|
// Use Pythagoras to see if drag length is larger than
|
||||||
|
// touch slop
|
||||||
|
mIsDragging = sqrt(((dx * dx) + (dy * dy)).toDouble()) >= mTouchSlop
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsDragging) {
|
||||||
|
mListener!!.onDrag(dx, dy)
|
||||||
|
mLastTouchX = x
|
||||||
|
mLastTouchY = y
|
||||||
|
|
||||||
|
mVelocityTracker?.addMovement(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_CANCEL -> {
|
||||||
|
mActivePointerId = INVALID_POINTER_ID
|
||||||
|
// Recycle Velocity Tracker
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mVelocityTracker!!.recycle()
|
||||||
|
mVelocityTracker = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_UP -> {
|
||||||
|
mActivePointerId = INVALID_POINTER_ID
|
||||||
|
if (mIsDragging) {
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mLastTouchX = getActiveX(ev)
|
||||||
|
mLastTouchY = getActiveY(ev)
|
||||||
|
|
||||||
|
// Compute velocity within the last 1000ms
|
||||||
|
mVelocityTracker!!.addMovement(ev)
|
||||||
|
mVelocityTracker!!.computeCurrentVelocity(1000)
|
||||||
|
|
||||||
|
val vX = mVelocityTracker!!.xVelocity
|
||||||
|
val vY = mVelocityTracker!!
|
||||||
|
.yVelocity
|
||||||
|
|
||||||
|
// If the velocity is greater than minVelocity, call
|
||||||
|
// listener
|
||||||
|
if (max(abs(vX.toDouble()), abs(vY.toDouble())) >= mMinimumVelocity) {
|
||||||
|
mListener!!.onFling(
|
||||||
|
mLastTouchX, mLastTouchY, -vX,
|
||||||
|
-vY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recycle Velocity Tracker
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mVelocityTracker!!.recycle()
|
||||||
|
mVelocityTracker = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_POINTER_UP -> {
|
||||||
|
val pointerIndex: Int = PhotoUtils.getPointerIndex(ev.action)
|
||||||
|
val pointerId = ev.getPointerId(pointerIndex)
|
||||||
|
if (pointerId == mActivePointerId) {
|
||||||
|
// This was our active pointer going up. Choose a new
|
||||||
|
// active pointer and adjust accordingly.
|
||||||
|
val newPointerIndex = if (pointerIndex == 0) 1 else 0
|
||||||
|
mActivePointerId = ev.getPointerId(newPointerIndex)
|
||||||
|
mLastTouchX = ev.getX(newPointerIndex)
|
||||||
|
mLastTouchY = ev.getY(newPointerIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mActivePointerIndex = ev
|
||||||
|
.findPointerIndex(
|
||||||
|
if (mActivePointerId != INVALID_POINTER_ID)
|
||||||
|
mActivePointerId
|
||||||
|
else
|
||||||
|
0
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
interface OnGestureListener {
|
||||||
|
fun onDrag(dx: Float, dy: Float)
|
||||||
|
|
||||||
|
fun onFling(
|
||||||
|
startX: Float, startY: Float, velocityX: Float,
|
||||||
|
velocityY: Float
|
||||||
|
)
|
||||||
|
|
||||||
|
fun onScale(scaleFactor: Float, focusX: Float, focusY: Float)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
import android.graphics.RectF
|
||||||
|
|
||||||
|
interface OnMatrixChangedListener {
|
||||||
|
/**
|
||||||
|
* Callback for when the Matrix displaying the Drawable has changed. This could be because
|
||||||
|
* the View's bounds have changed, or the user has zoomed.
|
||||||
|
*
|
||||||
|
* @param rect - Rectangle displaying the Drawable's new bounds.
|
||||||
|
*/
|
||||||
|
fun onMatrixChanged(rect: RectF?)
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
import android.widget.ImageView
|
||||||
|
|
||||||
|
interface OnOutsidePhotoTapListener {
|
||||||
|
/**
|
||||||
|
* The outside of the photo has been tapped
|
||||||
|
*/
|
||||||
|
fun onOutsidePhotoTap(imageView: ImageView?)
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
import android.widget.ImageView
|
||||||
|
|
||||||
|
interface OnPhotoTapListener {
|
||||||
|
fun onPhotoTap(view: ImageView?, x: Float, y: Float)
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
interface OnScaleChangedListener {
|
||||||
|
fun onScaleChange(scaleFactor: Float, focusX: Float, focusY: Float)
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
import android.view.MotionEvent
|
||||||
|
|
||||||
|
interface OnSingleFlingListener {
|
||||||
|
fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
interface OnViewDragListener {
|
||||||
|
fun onDrag(dx: Float, dy: Float)
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
interface OnViewTapListener {
|
||||||
|
fun onViewTap(view: View?, x: Float, y: Float)
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.ImageView.ScaleType
|
||||||
|
|
||||||
|
class PhotoUtils {
|
||||||
|
companion object {
|
||||||
|
fun checkZoomLevels(
|
||||||
|
minZoom: Float, midZoom: Float,
|
||||||
|
maxZoom: Float
|
||||||
|
) {
|
||||||
|
require(!(minZoom >= midZoom)) { "Minimum zoom has to be less than Medium zoom. Call setMinimumZoom() with a more appropriate value" }
|
||||||
|
require(!(midZoom >= maxZoom)) { "Medium zoom has to be less than Maximum zoom. Call setMaximumZoom() with a more appropriate value" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasDrawable(imageView: ImageView): Boolean {
|
||||||
|
return imageView.drawable != null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSupportedScaleType(scaleType: ScaleType?): Boolean {
|
||||||
|
if (scaleType == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (scaleType == ScaleType.MATRIX) {
|
||||||
|
throw IllegalStateException("Matrix scale type is not supported")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPointerIndex(action: Int): Int {
|
||||||
|
return (action and MotionEvent.ACTION_POINTER_INDEX_MASK) shr MotionEvent.ACTION_POINTER_INDEX_SHIFT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,222 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Matrix
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.GestureDetector
|
||||||
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
|
|
||||||
|
class PhotoView : AppCompatImageView {
|
||||||
|
private lateinit var attacher: PhotoViewAttacher
|
||||||
|
private var pendingScaleType: ScaleType? = null
|
||||||
|
|
||||||
|
constructor(context: Context) : this(context, null) {}
|
||||||
|
constructor(context: Context, attr: AttributeSet?) : this(context, null, 0) {}
|
||||||
|
constructor(context: Context, attr: AttributeSet?, defStyle: Int) : super(
|
||||||
|
context,
|
||||||
|
attr,
|
||||||
|
defStyle
|
||||||
|
) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init() {
|
||||||
|
attacher = PhotoViewAttacher(this)
|
||||||
|
//We always pose as a Matrix scale type, though we can change to another scale type
|
||||||
|
//via the attacher
|
||||||
|
super.setScaleType(ScaleType.MATRIX)
|
||||||
|
//apply the previously applied scale type
|
||||||
|
if (pendingScaleType != null) {
|
||||||
|
scaleType = pendingScaleType!!
|
||||||
|
pendingScaleType = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current [PhotoViewAttacher] for this view. Be wary of holding on to references
|
||||||
|
* to this attacher, as it has a reference to this view, which, if a reference is held in the
|
||||||
|
* wrong place, can cause memory leaks.
|
||||||
|
*
|
||||||
|
* @return the attacher.
|
||||||
|
*/
|
||||||
|
fun getAttacher(): PhotoViewAttacher {
|
||||||
|
return attacher
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getScaleType(): ScaleType {
|
||||||
|
return attacher.getScaleType()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getImageMatrix(): Matrix {
|
||||||
|
return attacher.getImageMatrix()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setOnLongClickListener(l: OnLongClickListener?) {
|
||||||
|
attacher.setOnLongClickListener(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setOnClickListener(l: OnClickListener?) {
|
||||||
|
attacher.setOnClickListener(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setScaleType(scaleType: ScaleType) {
|
||||||
|
if (attacher == null) {
|
||||||
|
pendingScaleType = scaleType
|
||||||
|
} else {
|
||||||
|
attacher.setScaleType(scaleType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setImageDrawable(drawable: Drawable?) {
|
||||||
|
super.setImageDrawable(drawable)
|
||||||
|
// setImageBitmap calls through to this method
|
||||||
|
if (attacher != null) {
|
||||||
|
attacher.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setImageResource(resId: Int) {
|
||||||
|
super.setImageResource(resId)
|
||||||
|
if (attacher != null) {
|
||||||
|
attacher.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setImageURI(uri: Uri?) {
|
||||||
|
super.setImageURI(uri)
|
||||||
|
if (attacher != null) {
|
||||||
|
attacher.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setFrame(l: Int, t: Int, r: Int, b: Int): Boolean {
|
||||||
|
val changed = super.setFrame(l, t, r, b)
|
||||||
|
if (changed) {
|
||||||
|
attacher.update()
|
||||||
|
}
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRotationTo(rotationDegree: Float) {
|
||||||
|
attacher.setRotationTo(rotationDegree)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRotationBy(rotationDegree: Float) {
|
||||||
|
attacher.setRotationBy(rotationDegree)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isZoomable(): Boolean {
|
||||||
|
return attacher.isZoomable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setZoomable(zoomable: Boolean) {
|
||||||
|
attacher.setZoomable(zoomable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDisplayRect(): RectF {
|
||||||
|
return attacher.getDisplayRect()!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDisplayMatrix(matrix: Matrix?) {
|
||||||
|
attacher.getDisplayMatrix(matrix!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDisplayMatrix(finalRectangle: Matrix?): Boolean {
|
||||||
|
return attacher.setDisplayMatrix(finalRectangle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSuppMatrix(matrix: Matrix?) {
|
||||||
|
attacher.getSuppMatrix(matrix!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSuppMatrix(matrix: Matrix?): Boolean {
|
||||||
|
return attacher.setDisplayMatrix(matrix)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMinimumScale(): Float {
|
||||||
|
return attacher.getMinimumScale()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMediumScale(): Float {
|
||||||
|
return attacher.getMediumScale()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMaximumScale(): Float {
|
||||||
|
return attacher.getMaximumScale()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getScale(): Float {
|
||||||
|
return attacher.getScale()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAllowParentInterceptOnEdge(allow: Boolean) {
|
||||||
|
attacher.setAllowParentInterceptOnEdge(allow)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMinimumScale(minimumScale: Float) {
|
||||||
|
attacher.setMinimumScale(minimumScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMediumScale(mediumScale: Float) {
|
||||||
|
attacher.setMediumScale(mediumScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMaximumScale(maximumScale: Float) {
|
||||||
|
attacher.setMaximumScale(maximumScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setScaleLevels(minimumScale: Float, mediumScale: Float, maximumScale: Float) {
|
||||||
|
attacher.setScaleLevels(minimumScale, mediumScale, maximumScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnMatrixChangeListener(listener: OnMatrixChangedListener) {
|
||||||
|
attacher.setOnMatrixChangeListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnPhotoTapListener(listener: OnPhotoTapListener) {
|
||||||
|
attacher.setOnPhotoTapListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnOutsidePhotoTapListener(listener: OnOutsidePhotoTapListener?) {
|
||||||
|
attacher.setOnOutsidePhotoTapListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnViewTapListener(listener: OnViewTapListener) {
|
||||||
|
attacher.setOnViewTapListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnViewDragListener(listener: OnViewDragListener) {
|
||||||
|
attacher.setOnViewDragListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setScale(scale: Float) {
|
||||||
|
attacher.setScale(scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setScale(scale: Float, animate: Boolean) {
|
||||||
|
attacher.setScale(scale, animate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setScale(scale: Float, focalX: Float, focalY: Float, animate: Boolean) {
|
||||||
|
attacher.setScale(scale, focalX, focalY, animate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setZoomTransitionDuration(milliseconds: Int) {
|
||||||
|
attacher.setZoomTransitionDuration(milliseconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnDoubleTapListener(onDoubleTapListener: GestureDetector.OnDoubleTapListener?) {
|
||||||
|
attacher.setOnDoubleTapListener(onDoubleTapListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnScaleChangeListener(onScaleChangedListener: OnScaleChangedListener?) {
|
||||||
|
attacher.setOnScaleChangeListener(onScaleChangedListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnSingleFlingListener(onSingleFlingListener: OnSingleFlingListener?) {
|
||||||
|
attacher.setOnSingleFlingListener(onSingleFlingListener)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,779 @@
|
|||||||
|
package com.tenlionsoft.medialib.base.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Matrix
|
||||||
|
import android.graphics.Matrix.ScaleToFit
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.view.GestureDetector
|
||||||
|
import android.view.GestureDetector.SimpleOnGestureListener
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.OnLongClickListener
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
|
import android.view.animation.Interpolator
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.ImageView.ScaleType
|
||||||
|
import android.widget.OverScroller
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
class PhotoViewAttacher : View.OnTouchListener, View.OnLayoutChangeListener {
|
||||||
|
var DEFAULT_MAX_SCALE: Float = 3.0f
|
||||||
|
var DEFAULT_MID_SCALE: Float = 1.75f
|
||||||
|
var DEFAULT_MIN_SCALE: Float = 1.0f
|
||||||
|
var DEFAULT_ZOOM_DURATION: Int = 200
|
||||||
|
|
||||||
|
val EDGE_NONE: Int = -1
|
||||||
|
val EDGE_LEFT: Int = 0
|
||||||
|
val EDGE_RIGHT: Int = 1
|
||||||
|
val EDGE_BOTH: Int = 2
|
||||||
|
var SINGLE_TOUCH: Int = 1
|
||||||
|
|
||||||
|
private var mInterpolator: Interpolator = AccelerateDecelerateInterpolator()
|
||||||
|
private var mZoomDuration = DEFAULT_ZOOM_DURATION
|
||||||
|
private var mMinScale = DEFAULT_MIN_SCALE
|
||||||
|
private var mMidScale = DEFAULT_MID_SCALE
|
||||||
|
private var mMaxScale = DEFAULT_MAX_SCALE
|
||||||
|
|
||||||
|
private var mAllowParentInterceptOnEdge = true
|
||||||
|
private var mBlockParentIntercept = false
|
||||||
|
|
||||||
|
private var mImageView: ImageView? = null
|
||||||
|
|
||||||
|
// Gesture Detectors
|
||||||
|
private lateinit var mGestureDetector: GestureDetector
|
||||||
|
private lateinit var mScaleDragDetector: CustomGestureDetector
|
||||||
|
|
||||||
|
// These are set so we don't keep allocating them on the heap
|
||||||
|
private val mBaseMatrix = Matrix()
|
||||||
|
private val mDrawMatrix = Matrix()
|
||||||
|
private val mSuppMatrix = Matrix()
|
||||||
|
private val mDisplayRect = RectF()
|
||||||
|
private val mMatrixValues = FloatArray(9)
|
||||||
|
|
||||||
|
// Listeners
|
||||||
|
private var mMatrixChangeListener: OnMatrixChangedListener? = null
|
||||||
|
private var mPhotoTapListener: OnPhotoTapListener? = null
|
||||||
|
private var mOutsidePhotoTapListener: OnOutsidePhotoTapListener? = null
|
||||||
|
private var mViewTapListener: OnViewTapListener? = null
|
||||||
|
private var mOnClickListener: View.OnClickListener? = null
|
||||||
|
private var mLongClickListener: OnLongClickListener? = null
|
||||||
|
private var mScaleChangeListener: OnScaleChangedListener? = null
|
||||||
|
private var mSingleFlingListener: OnSingleFlingListener? = null
|
||||||
|
private var mOnViewDragListener: OnViewDragListener? = null
|
||||||
|
|
||||||
|
private var mCurrentFlingRunnable: PhotoViewAttacher.FlingRunnable? =
|
||||||
|
null
|
||||||
|
private var mScrollEdge = EDGE_BOTH
|
||||||
|
private var mBaseRotation = 0f
|
||||||
|
|
||||||
|
private var mZoomEnabled = true
|
||||||
|
private var mScaleType = ScaleType.FIT_CENTER
|
||||||
|
|
||||||
|
|
||||||
|
private val onGestureListener: OnGestureListener = object : OnGestureListener {
|
||||||
|
override fun onDrag(dx: Float, dy: Float) {
|
||||||
|
if (mScaleDragDetector.isScaling()) {
|
||||||
|
return // Do not drag if we are already scaling
|
||||||
|
}
|
||||||
|
mOnViewDragListener?.onDrag(dx, dy)
|
||||||
|
mSuppMatrix.postTranslate(dx, dy)
|
||||||
|
checkAndDisplayMatrix()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here we decide whether to let the ImageView's parent to start taking
|
||||||
|
* over the touch event.
|
||||||
|
*
|
||||||
|
* First we check whether this function is enabled. We never want the
|
||||||
|
* parent to take over if we're scaling. We then check the edge we're
|
||||||
|
* on, and the direction of the scroll (i.e. if we're pulling against
|
||||||
|
* the edge, aka 'overscrolling', let the parent take over).
|
||||||
|
*/
|
||||||
|
val parent = mImageView!!.parent
|
||||||
|
if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) {
|
||||||
|
if (mScrollEdge == EDGE_BOTH || (mScrollEdge == EDGE_LEFT && dx >= 1f)
|
||||||
|
|| (mScrollEdge == EDGE_RIGHT && dx <= -1f)
|
||||||
|
) {
|
||||||
|
parent?.requestDisallowInterceptTouchEvent(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parent?.requestDisallowInterceptTouchEvent(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFling(startX: Float, startY: Float, velocityX: Float, velocityY: Float) {
|
||||||
|
mCurrentFlingRunnable = FlingRunnable(mImageView!!.context)
|
||||||
|
mCurrentFlingRunnable!!.fling(
|
||||||
|
getImageViewWidth(mImageView!!),
|
||||||
|
getImageViewHeight(mImageView!!), velocityX.toInt(), velocityY.toInt()
|
||||||
|
)
|
||||||
|
mImageView?.post(mCurrentFlingRunnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScale(scaleFactor: Float, focusX: Float, focusY: Float) {
|
||||||
|
if ((getScale() < mMaxScale || scaleFactor < 1f) && (getScale() > mMinScale || scaleFactor > 1f)) {
|
||||||
|
mScaleChangeListener?.onScaleChange(scaleFactor, focusX, focusY)
|
||||||
|
mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY)
|
||||||
|
checkAndDisplayMatrix()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(imageView: ImageView) {
|
||||||
|
|
||||||
|
mImageView = imageView
|
||||||
|
imageView.setOnTouchListener(this)
|
||||||
|
imageView.addOnLayoutChangeListener(this)
|
||||||
|
if (imageView.isInEditMode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mBaseRotation = 0.0f
|
||||||
|
// Create Gesture Detectors...
|
||||||
|
mScaleDragDetector = CustomGestureDetector(imageView.context, onGestureListener)
|
||||||
|
mGestureDetector = GestureDetector(imageView.context, object : SimpleOnGestureListener() {
|
||||||
|
// forward long click listener
|
||||||
|
override fun onLongPress(e: MotionEvent) {
|
||||||
|
mLongClickListener?.onLongClick(mImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFling(
|
||||||
|
e1: MotionEvent?, e2: MotionEvent,
|
||||||
|
velocityX: Float, velocityY: Float
|
||||||
|
): Boolean {
|
||||||
|
if (mSingleFlingListener != null) {
|
||||||
|
if (getScale() > DEFAULT_MIN_SCALE) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (e1!!.pointerCount > SINGLE_TOUCH
|
||||||
|
|| e2.pointerCount > SINGLE_TOUCH
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return mSingleFlingListener!!.onFling(e1, e2, velocityX, velocityY)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mGestureDetector.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener {
|
||||||
|
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
|
||||||
|
mOnClickListener?.onClick(mImageView)
|
||||||
|
val displayRect = getDisplayRect()
|
||||||
|
val x = e.x
|
||||||
|
val y = e.y
|
||||||
|
mViewTapListener?.onViewTap(mImageView, x, y)
|
||||||
|
if (displayRect != null) {
|
||||||
|
// Check to see if the user tapped on the photo
|
||||||
|
if (displayRect.contains(x, y)) {
|
||||||
|
val xResult = ((x - displayRect.left)
|
||||||
|
/ displayRect.width())
|
||||||
|
val yResult = ((y - displayRect.top)
|
||||||
|
/ displayRect.height())
|
||||||
|
mPhotoTapListener?.onPhotoTap(mImageView, xResult, yResult)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
mOutsidePhotoTapListener?.onOutsidePhotoTap(mImageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDoubleTap(ev: MotionEvent): Boolean {
|
||||||
|
try {
|
||||||
|
val scale = getScale()
|
||||||
|
val x = ev.x
|
||||||
|
val y = ev.y
|
||||||
|
if (scale < getMediumScale()) {
|
||||||
|
setScale(getMediumScale(), x, y, true)
|
||||||
|
} else if (scale >= getMediumScale() && scale < getMaximumScale()) {
|
||||||
|
setScale(getMaximumScale(), x, y, true)
|
||||||
|
} else {
|
||||||
|
setScale(getMinimumScale(), x, y, true)
|
||||||
|
}
|
||||||
|
} catch (e: ArrayIndexOutOfBoundsException) {
|
||||||
|
// Can sometimes happen when getX() and getY() is called
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDoubleTapEvent(e: MotionEvent): Boolean {
|
||||||
|
// Wait for the confirmed onDoubleTap() instead
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun setOnDoubleTapListener(newOnDoubleTapListener: GestureDetector.OnDoubleTapListener?) {
|
||||||
|
mGestureDetector!!.setOnDoubleTapListener(newOnDoubleTapListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnScaleChangeListener(onScaleChangeListener: OnScaleChangedListener?) {
|
||||||
|
this.mScaleChangeListener = onScaleChangeListener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnSingleFlingListener(onSingleFlingListener: OnSingleFlingListener?) {
|
||||||
|
this.mSingleFlingListener = onSingleFlingListener
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("")
|
||||||
|
fun isZoomEnabled(): Boolean {
|
||||||
|
return mZoomEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDisplayRect(): RectF? {
|
||||||
|
checkMatrixBounds()
|
||||||
|
return getDisplayRect(getDrawMatrix())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDisplayMatrix(finalMatrix: Matrix?): Boolean {
|
||||||
|
requireNotNull(finalMatrix) { "Matrix cannot be null" }
|
||||||
|
if (mImageView!!.drawable == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mSuppMatrix.set(finalMatrix)
|
||||||
|
checkAndDisplayMatrix()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setBaseRotation(degrees: Float) {
|
||||||
|
mBaseRotation = degrees % 360
|
||||||
|
update()
|
||||||
|
setRotationBy(mBaseRotation)
|
||||||
|
checkAndDisplayMatrix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRotationTo(degrees: Float) {
|
||||||
|
mSuppMatrix.setRotate(degrees % 360)
|
||||||
|
checkAndDisplayMatrix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRotationBy(degrees: Float) {
|
||||||
|
mSuppMatrix.postRotate(degrees % 360)
|
||||||
|
checkAndDisplayMatrix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMinimumScale(): Float {
|
||||||
|
return mMinScale
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMediumScale(): Float {
|
||||||
|
return mMidScale
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMaximumScale(): Float {
|
||||||
|
return mMaxScale
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getScale(): Float {
|
||||||
|
return sqrt(
|
||||||
|
(getValue(
|
||||||
|
mSuppMatrix,
|
||||||
|
Matrix.MSCALE_X
|
||||||
|
).pow(2.0F) + getValue(
|
||||||
|
mSuppMatrix,
|
||||||
|
Matrix.MSKEW_Y
|
||||||
|
).pow(2.0F)).toDouble()
|
||||||
|
).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getScaleType(): ScaleType {
|
||||||
|
return mScaleType
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutChange(
|
||||||
|
v: View?,
|
||||||
|
left: Int,
|
||||||
|
top: Int,
|
||||||
|
right: Int,
|
||||||
|
bottom: Int,
|
||||||
|
oldLeft: Int,
|
||||||
|
oldTop: Int,
|
||||||
|
oldRight: Int,
|
||||||
|
oldBottom: Int
|
||||||
|
) {
|
||||||
|
// Update our base matrix, as the bounds have changed
|
||||||
|
if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
|
||||||
|
updateBaseMatrix(mImageView!!.drawable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouch(v: View, ev: MotionEvent): Boolean {
|
||||||
|
var handled = false
|
||||||
|
if (mZoomEnabled && PhotoUtils.hasDrawable(v as ImageView)) {
|
||||||
|
when (ev.action) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
val parent = v.getParent()
|
||||||
|
// First, disable the Parent from intercepting the touch
|
||||||
|
// event
|
||||||
|
parent?.requestDisallowInterceptTouchEvent(true)
|
||||||
|
// If we're flinging, and the user presses down, cancel
|
||||||
|
// fling
|
||||||
|
cancelFling()
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> // If the user has zoomed less than min scale, zoom back
|
||||||
|
// to min scale
|
||||||
|
if (getScale() < mMinScale) {
|
||||||
|
val rect = getDisplayRect()
|
||||||
|
if (rect != null) {
|
||||||
|
v.post(
|
||||||
|
AnimatedZoomRunnable(
|
||||||
|
getScale(), mMinScale,
|
||||||
|
rect.centerX(), rect.centerY()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
handled = true
|
||||||
|
}
|
||||||
|
} else if (getScale() > mMaxScale) {
|
||||||
|
val rect = getDisplayRect()
|
||||||
|
if (rect != null) {
|
||||||
|
v.post(
|
||||||
|
AnimatedZoomRunnable(
|
||||||
|
getScale(), mMaxScale,
|
||||||
|
rect.centerX(), rect.centerY()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
handled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try the Scale/Drag detector
|
||||||
|
if (mScaleDragDetector != null) {
|
||||||
|
val wasScaling = mScaleDragDetector.isScaling()
|
||||||
|
val wasDragging = mScaleDragDetector.isDragging()
|
||||||
|
handled = mScaleDragDetector.onTouchEvent(ev)
|
||||||
|
val didntScale = !wasScaling && !mScaleDragDetector.isScaling()
|
||||||
|
val didntDrag = !wasDragging && !mScaleDragDetector.isDragging()
|
||||||
|
mBlockParentIntercept = didntScale && didntDrag
|
||||||
|
}
|
||||||
|
// Check to see if the user double tapped
|
||||||
|
if (mGestureDetector != null && mGestureDetector.onTouchEvent(ev)) {
|
||||||
|
handled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAllowParentInterceptOnEdge(allow: Boolean) {
|
||||||
|
mAllowParentInterceptOnEdge = allow
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMinimumScale(minimumScale: Float) {
|
||||||
|
PhotoUtils.checkZoomLevels(minimumScale, mMidScale, mMaxScale)
|
||||||
|
mMinScale = minimumScale
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMediumScale(mediumScale: Float) {
|
||||||
|
PhotoUtils.checkZoomLevels(mMinScale, mediumScale, mMaxScale)
|
||||||
|
mMidScale = mediumScale
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMaximumScale(maximumScale: Float) {
|
||||||
|
PhotoUtils.checkZoomLevels(mMinScale, mMidScale, maximumScale)
|
||||||
|
mMaxScale = maximumScale
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setScaleLevels(minimumScale: Float, mediumScale: Float, maximumScale: Float) {
|
||||||
|
PhotoUtils.checkZoomLevels(minimumScale, mediumScale, maximumScale)
|
||||||
|
mMinScale = minimumScale
|
||||||
|
mMidScale = mediumScale
|
||||||
|
mMaxScale = maximumScale
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnLongClickListener(listener: OnLongClickListener?) {
|
||||||
|
mLongClickListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnClickListener(listener: View.OnClickListener?) {
|
||||||
|
mOnClickListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnMatrixChangeListener(listener: OnMatrixChangedListener) {
|
||||||
|
mMatrixChangeListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnPhotoTapListener(listener: OnPhotoTapListener) {
|
||||||
|
mPhotoTapListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnOutsidePhotoTapListener(mOutsidePhotoTapListener: OnOutsidePhotoTapListener?) {
|
||||||
|
this.mOutsidePhotoTapListener = mOutsidePhotoTapListener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnViewTapListener(listener: OnViewTapListener) {
|
||||||
|
mViewTapListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnViewDragListener(listener: OnViewDragListener) {
|
||||||
|
mOnViewDragListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setScale(scale: Float) {
|
||||||
|
setScale(scale, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setScale(scale: Float, animate: Boolean) {
|
||||||
|
setScale(
|
||||||
|
scale,
|
||||||
|
((mImageView!!.right) / 2).toFloat(),
|
||||||
|
((mImageView!!.bottom) / 2).toFloat(),
|
||||||
|
animate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setScale(
|
||||||
|
scale: Float, focalX: Float, focalY: Float,
|
||||||
|
animate: Boolean
|
||||||
|
) {
|
||||||
|
// Check to see if the scale is within bounds
|
||||||
|
require(!(scale < mMinScale || scale > mMaxScale)) { "Scale must be within the range of minScale and maxScale" }
|
||||||
|
if (animate) {
|
||||||
|
mImageView!!.post(
|
||||||
|
AnimatedZoomRunnable(
|
||||||
|
getScale(), scale,
|
||||||
|
focalX, focalY
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
mSuppMatrix.setScale(scale, scale, focalX, focalY)
|
||||||
|
checkAndDisplayMatrix()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the zoom interpolator
|
||||||
|
*
|
||||||
|
* @param interpolator the zoom interpolator
|
||||||
|
*/
|
||||||
|
fun setZoomInterpolator(interpolator: Interpolator) {
|
||||||
|
mInterpolator = interpolator
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setScaleType(scaleType: ScaleType) {
|
||||||
|
if (PhotoUtils.isSupportedScaleType(scaleType) && scaleType != mScaleType) {
|
||||||
|
mScaleType = scaleType
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isZoomable(): Boolean {
|
||||||
|
return mZoomEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setZoomable(zoomable: Boolean) {
|
||||||
|
mZoomEnabled = zoomable
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update() {
|
||||||
|
if (mZoomEnabled) {
|
||||||
|
// Update the base matrix using the current drawable
|
||||||
|
updateBaseMatrix(mImageView!!.drawable)
|
||||||
|
} else {
|
||||||
|
// Reset the Matrix...
|
||||||
|
resetMatrix()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the display matrix
|
||||||
|
*
|
||||||
|
* @param matrix target matrix to copy to
|
||||||
|
*/
|
||||||
|
fun getDisplayMatrix(matrix: Matrix) {
|
||||||
|
matrix.set(getDrawMatrix())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current support matrix
|
||||||
|
*/
|
||||||
|
fun getSuppMatrix(matrix: Matrix) {
|
||||||
|
matrix.set(mSuppMatrix)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDrawMatrix(): Matrix {
|
||||||
|
mDrawMatrix.set(mBaseMatrix)
|
||||||
|
mDrawMatrix.postConcat(mSuppMatrix)
|
||||||
|
return mDrawMatrix
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getImageMatrix(): Matrix {
|
||||||
|
return mDrawMatrix
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setZoomTransitionDuration(milliseconds: Int) {
|
||||||
|
this.mZoomDuration = milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that 'unpacks' a Matrix and returns the required value
|
||||||
|
*
|
||||||
|
* @param matrix Matrix to unpack
|
||||||
|
* @param whichValue Which value from Matrix.M* to return
|
||||||
|
* @return returned value
|
||||||
|
*/
|
||||||
|
private fun getValue(matrix: Matrix, whichValue: Int): Float {
|
||||||
|
matrix.getValues(mMatrixValues)
|
||||||
|
return mMatrixValues[whichValue]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the Matrix back to FIT_CENTER, and then displays its contents
|
||||||
|
*/
|
||||||
|
private fun resetMatrix() {
|
||||||
|
mSuppMatrix.reset()
|
||||||
|
setRotationBy(mBaseRotation)
|
||||||
|
setImageViewMatrix(getDrawMatrix())
|
||||||
|
checkMatrixBounds()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setImageViewMatrix(matrix: Matrix) {
|
||||||
|
mImageView!!.imageMatrix = matrix
|
||||||
|
// Call MatrixChangedListener if needed
|
||||||
|
if (mMatrixChangeListener != null) {
|
||||||
|
val displayRect = getDisplayRect(matrix)
|
||||||
|
if (displayRect != null) {
|
||||||
|
mMatrixChangeListener!!.onMatrixChanged(displayRect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that simply checks the Matrix, and then displays the result
|
||||||
|
*/
|
||||||
|
private fun checkAndDisplayMatrix() {
|
||||||
|
if (checkMatrixBounds()) {
|
||||||
|
setImageViewMatrix(getDrawMatrix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that maps the supplied Matrix to the current Drawable
|
||||||
|
*
|
||||||
|
* @param matrix - Matrix to map Drawable against
|
||||||
|
* @return RectF - Displayed Rectangle
|
||||||
|
*/
|
||||||
|
private fun getDisplayRect(matrix: Matrix): RectF? {
|
||||||
|
val d = mImageView!!.drawable
|
||||||
|
if (d != null) {
|
||||||
|
mDisplayRect[0f, 0f, d.intrinsicWidth.toFloat()] = d.intrinsicHeight.toFloat()
|
||||||
|
matrix.mapRect(mDisplayRect)
|
||||||
|
return mDisplayRect
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate Matrix for FIT_CENTER
|
||||||
|
*
|
||||||
|
* @param drawable - Drawable being displayed
|
||||||
|
*/
|
||||||
|
private fun updateBaseMatrix(drawable: Drawable?) {
|
||||||
|
if (drawable == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val viewWidth = getImageViewWidth(mImageView!!).toFloat()
|
||||||
|
val viewHeight = getImageViewHeight(mImageView!!).toFloat()
|
||||||
|
val drawableWidth = drawable.intrinsicWidth
|
||||||
|
val drawableHeight = drawable.intrinsicHeight
|
||||||
|
mBaseMatrix.reset()
|
||||||
|
val widthScale = viewWidth / drawableWidth
|
||||||
|
val heightScale = viewHeight / drawableHeight
|
||||||
|
if (mScaleType == ScaleType.CENTER) {
|
||||||
|
mBaseMatrix.postTranslate(
|
||||||
|
(viewWidth - drawableWidth) / 2f,
|
||||||
|
(viewHeight - drawableHeight) / 2f
|
||||||
|
)
|
||||||
|
} else if (mScaleType == ScaleType.CENTER_CROP) {
|
||||||
|
val scale =
|
||||||
|
max(widthScale.toDouble(), heightScale.toDouble()).toFloat()
|
||||||
|
mBaseMatrix.postScale(scale, scale)
|
||||||
|
mBaseMatrix.postTranslate(
|
||||||
|
(viewWidth - drawableWidth * scale) / 2f,
|
||||||
|
(viewHeight - drawableHeight * scale) / 2f
|
||||||
|
)
|
||||||
|
} else if (mScaleType == ScaleType.CENTER_INSIDE) {
|
||||||
|
val scale =
|
||||||
|
min(1.0, min(widthScale.toDouble(), heightScale.toDouble())).toFloat()
|
||||||
|
mBaseMatrix.postScale(scale, scale)
|
||||||
|
mBaseMatrix.postTranslate(
|
||||||
|
(viewWidth - drawableWidth * scale) / 2f,
|
||||||
|
(viewHeight - drawableHeight * scale) / 2f
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
var mTempSrc = RectF(0f, 0f, drawableWidth.toFloat(), drawableHeight.toFloat())
|
||||||
|
val mTempDst = RectF(0f, 0f, viewWidth, viewHeight)
|
||||||
|
if (mBaseRotation.toInt() % 180 != 0) {
|
||||||
|
mTempSrc = RectF(0f, 0f, drawableHeight.toFloat(), drawableWidth.toFloat())
|
||||||
|
}
|
||||||
|
when (mScaleType) {
|
||||||
|
ScaleType.FIT_CENTER -> mBaseMatrix.setRectToRect(
|
||||||
|
mTempSrc,
|
||||||
|
mTempDst,
|
||||||
|
ScaleToFit.CENTER
|
||||||
|
)
|
||||||
|
|
||||||
|
ScaleType.FIT_START -> mBaseMatrix.setRectToRect(
|
||||||
|
mTempSrc,
|
||||||
|
mTempDst,
|
||||||
|
ScaleToFit.START
|
||||||
|
)
|
||||||
|
|
||||||
|
ScaleType.FIT_END -> mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END)
|
||||||
|
ScaleType.FIT_XY -> mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetMatrix()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkMatrixBounds(): Boolean {
|
||||||
|
val rect = getDisplayRect(getDrawMatrix()) ?: return false
|
||||||
|
val height = rect.height()
|
||||||
|
val width = rect.width()
|
||||||
|
var deltaX = 0f
|
||||||
|
var deltaY = 0f
|
||||||
|
val viewHeight = getImageViewHeight(mImageView!!)
|
||||||
|
if (height <= viewHeight) {
|
||||||
|
deltaY = when (mScaleType) {
|
||||||
|
ScaleType.FIT_START -> -rect.top
|
||||||
|
ScaleType.FIT_END -> viewHeight - height - rect.top
|
||||||
|
else -> (viewHeight - height) / 2 - rect.top
|
||||||
|
}
|
||||||
|
} else if (rect.top > 0) {
|
||||||
|
deltaY = -rect.top
|
||||||
|
} else if (rect.bottom < viewHeight) {
|
||||||
|
deltaY = viewHeight - rect.bottom
|
||||||
|
}
|
||||||
|
val viewWidth = getImageViewWidth(mImageView!!)
|
||||||
|
if (width <= viewWidth) {
|
||||||
|
deltaX = when (mScaleType) {
|
||||||
|
ScaleType.FIT_START -> -rect.left
|
||||||
|
ScaleType.FIT_END -> viewWidth - width - rect.left
|
||||||
|
else -> (viewWidth - width) / 2 - rect.left
|
||||||
|
}
|
||||||
|
mScrollEdge = EDGE_BOTH
|
||||||
|
} else if (rect.left > 0) {
|
||||||
|
mScrollEdge = EDGE_LEFT
|
||||||
|
deltaX = -rect.left
|
||||||
|
} else if (rect.right < viewWidth) {
|
||||||
|
deltaX = viewWidth - rect.right
|
||||||
|
mScrollEdge = EDGE_RIGHT
|
||||||
|
} else {
|
||||||
|
mScrollEdge = EDGE_NONE
|
||||||
|
}
|
||||||
|
// Finally actually translate the matrix
|
||||||
|
mSuppMatrix.postTranslate(deltaX, deltaY)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getImageViewWidth(imageView: ImageView): Int {
|
||||||
|
return imageView.width - imageView.paddingLeft - imageView.paddingRight
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getImageViewHeight(imageView: ImageView): Int {
|
||||||
|
return imageView.height - imageView.paddingTop - imageView.paddingBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelFling() {
|
||||||
|
if (mCurrentFlingRunnable != null) {
|
||||||
|
mCurrentFlingRunnable!!.cancelFling()
|
||||||
|
mCurrentFlingRunnable = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class AnimatedZoomRunnable(
|
||||||
|
private val mZoomStart: Float, private val mZoomEnd: Float,
|
||||||
|
private val mFocalX: Float, private val mFocalY: Float
|
||||||
|
) : Runnable {
|
||||||
|
private val mStartTime = System.currentTimeMillis()
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
val t = interpolate()
|
||||||
|
val scale = mZoomStart + t * (mZoomEnd - mZoomStart)
|
||||||
|
val deltaScale: Float = scale / getScale()
|
||||||
|
onGestureListener.onScale(deltaScale, mFocalX, mFocalY)
|
||||||
|
// We haven't hit our target scale yet, so post ourselves again
|
||||||
|
if (t < 1f) {
|
||||||
|
Compat.postOnAnimation(mImageView!!, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interpolate(): Float {
|
||||||
|
var t: Float = 1f * (System.currentTimeMillis() - mStartTime) / mZoomDuration
|
||||||
|
t = min(1.0, t.toDouble()).toFloat()
|
||||||
|
t = mInterpolator.getInterpolation(t)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class FlingRunnable(context: Context?) : Runnable {
|
||||||
|
private val mScroller = OverScroller(context)
|
||||||
|
private var mCurrentX = 0
|
||||||
|
private var mCurrentY = 0
|
||||||
|
|
||||||
|
fun cancelFling() {
|
||||||
|
mScroller.forceFinished(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fling(
|
||||||
|
viewWidth: Int, viewHeight: Int, velocityX: Int,
|
||||||
|
velocityY: Int
|
||||||
|
) {
|
||||||
|
val rect: RectF = getDisplayRect() ?: return
|
||||||
|
val startX = Math.round(-rect.left)
|
||||||
|
val minX: Int
|
||||||
|
val maxX: Int
|
||||||
|
val minY: Int
|
||||||
|
val maxY: Int
|
||||||
|
if (viewWidth < rect.width()) {
|
||||||
|
minX = 0
|
||||||
|
maxX = Math.round(rect.width() - viewWidth)
|
||||||
|
} else {
|
||||||
|
maxX = startX
|
||||||
|
minX = maxX
|
||||||
|
}
|
||||||
|
val startY = Math.round(-rect.top)
|
||||||
|
if (viewHeight < rect.height()) {
|
||||||
|
minY = 0
|
||||||
|
maxY = Math.round(rect.height() - viewHeight)
|
||||||
|
} else {
|
||||||
|
maxY = startY
|
||||||
|
minY = maxY
|
||||||
|
}
|
||||||
|
mCurrentX = startX
|
||||||
|
mCurrentY = startY
|
||||||
|
// If we actually can move, fling the scroller
|
||||||
|
if (startX != maxX || startY != maxY) {
|
||||||
|
mScroller.fling(
|
||||||
|
startX, startY, velocityX, velocityY, minX,
|
||||||
|
maxX, minY, maxY, 0, 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
if (mScroller.isFinished) {
|
||||||
|
return // remaining post that should not be handled
|
||||||
|
}
|
||||||
|
if (mScroller.computeScrollOffset()) {
|
||||||
|
val newX = mScroller.currX
|
||||||
|
val newY = mScroller.currY
|
||||||
|
mSuppMatrix.postTranslate(
|
||||||
|
(mCurrentX - newX).toFloat(),
|
||||||
|
(mCurrentY - newY).toFloat()
|
||||||
|
)
|
||||||
|
checkAndDisplayMatrix()
|
||||||
|
mCurrentX = newX
|
||||||
|
mCurrentY = newY
|
||||||
|
// Post On animation
|
||||||
|
Compat.postOnAnimation(mImageView!!, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
32
medialib/src/main/res/layout/activity_simple_photo.xml
Normal file
32
medialib/src/main/res/layout/activity_simple_photo.xml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.viewpager.widget.ViewPager
|
||||||
|
android:id="@+id/vp_images"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_count"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginBottom="20dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
tools:text="1 / 2" />
|
||||||
|
</RelativeLayout>
|
||||||
|
</layout>
|
21
medialib/src/main/res/layout/activity_simple_video.xml
Normal file
21
medialib/src/main/res/layout/activity_simple_video.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".base.SimpleVideoActivity">
|
||||||
|
|
||||||
|
<com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer
|
||||||
|
android:id="@+id/s_video"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
11
medialib/src/main/res/layout/item_photo.xml
Normal file
11
medialib/src/main/res/layout/item_photo.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.tenlionsoft.medialib.base.widget.PhotoView
|
||||||
|
android:id="@+id/pv_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</LinearLayout>
|
16
medialib/src/main/res/values/attrs.xml
Normal file
16
medialib/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<declare-styleable name="VideoView">
|
||||||
|
<attr name="looping" format="boolean"/>
|
||||||
|
<attr name="enableAudioFocus" format="boolean"/>
|
||||||
|
<attr name="screenScaleType" format="dimension">
|
||||||
|
<enum name="type_default" value="0"/>
|
||||||
|
<enum name="type_16_9" value="1"/>
|
||||||
|
<enum name="type_4_3" value="2"/>
|
||||||
|
<enum name="type_match_parent" value="3"/>
|
||||||
|
<enum name="type_original" value="4"/>
|
||||||
|
<enum name="type_center_crop" value="5"/>
|
||||||
|
</attr>
|
||||||
|
<attr name="playerBackgroundColor" format="color"/>
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
6
medialib/src/main/res/values/colors.xml
Normal file
6
medialib/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="dkplayer_theme_color">#9F1512</color>
|
||||||
|
<color name="dkplayer_theme_color_translucent">#999F1512</color>
|
||||||
|
<color name="dkplayer_background_color">#4D000000</color>
|
||||||
|
</resources>
|
12
medialib/src/main/res/values/dimens.xml
Normal file
12
medialib/src/main/res/values/dimens.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<dimen name="dkplayer_controller_height">46dp</dimen>
|
||||||
|
<dimen name="dkplayer_controller_icon_padding">12dp</dimen>
|
||||||
|
<dimen name="dkplayer_controller_text_size">16sp</dimen>
|
||||||
|
<dimen name="dkplayer_default_spacing">10dp</dimen>
|
||||||
|
<dimen name="dkplayer_controller_seekbar_size_n">14dp</dimen>
|
||||||
|
<dimen name="dkplayer_controller_seekbar_size_s">14dp</dimen>
|
||||||
|
<dimen name="dkplayer_controller_seekbar_max_size">1dp</dimen>
|
||||||
|
<dimen name="dkplayer_play_btn_size">50dp</dimen>
|
||||||
|
<dimen name="dkplayer_controller_time_text_size">14sp</dimen>
|
||||||
|
</resources>
|
11
medialib/src/main/res/values/strings.xml
Normal file
11
medialib/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="dkplayer_replay">重新播放</string>
|
||||||
|
<string name="dkplayer_lock_tip">请先解锁屏幕!</string>
|
||||||
|
<string name="dkplayer_unlocked">已解锁</string>
|
||||||
|
<string name="dkplayer_locked">已锁定</string>
|
||||||
|
<string name="dkplayer_error_message">出了点小问题,请稍后重试</string>
|
||||||
|
<string name="dkplayer_retry"> 重 试 </string>
|
||||||
|
<string name="dkplayer_continue_play">继续播放</string>
|
||||||
|
<string name="dkplayer_wifi_tip">您正在使用移动网络,继续播放将消耗流量</string>
|
||||||
|
<string name="img_position">%1$d  /  %2$d</string>
|
||||||
|
</resources>
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.tenlionsoft.medialib;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
@ -17,9 +17,11 @@ dependencyResolutionManagement {
|
|||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven(url = "https://jitpack.io")
|
maven(url = "https://jitpack.io")
|
||||||
|
maven(url = "https://maven.aliyun.com/repository/public")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "aimz_k"
|
rootProject.name = "aimz_k"
|
||||||
include(":app")
|
include(":app")
|
||||||
include(":baselib")
|
include(":baselib")
|
||||||
|
include(":medialib")
|
||||||
|
Loading…
Reference in New Issue
Block a user