guide初步

This commit is contained in:
renhaoting 2025-12-16 15:26:22 +08:00
parent e0ede99c67
commit 994d205a7e
23 changed files with 1243 additions and 110 deletions

View File

@ -88,6 +88,7 @@ dependencies {
implementation(libs.okhttp.logging)
implementation(libs.retrofit)
implementation(libs.retrofit.kotlin.serialization)
//implementation 'com.github.hyy920109:GuidePro:1.0.3'
}

View File

@ -1,12 +1,9 @@
package com.gamedog.vididin.main
import android.app.Activity
import com.ama.core.architecture.util.eventbus.NotifyMan
import com.ama.core.architecture.util.setOnClickBatch
import com.ama.core.architecture.widget.BindingDialog
import com.gamedog.vididin.VididinEvents
import com.vididin.real.money.game.databinding.DialogBeginnerGiftBinding
import com.gamedog.vididin.router.Router
class BeginnerGiftDialog(activity: Activity) : BindingDialog<DialogBeginnerGiftBinding>(activity, DialogBeginnerGiftBinding::inflate) {
@ -26,10 +23,10 @@ class BeginnerGiftDialog(activity: Activity) : BindingDialog<DialogBeginnerGiftB
setOnClickBatch(tvAction) {
when (this) {
tvAction -> {
/*if (mActivity is MainActivity) {
if (mActivity is MainActivity) {
(mActivity as MainActivity).switchTab(1)
}
NotifyMan.instance().sendEvent(VididinEvents.EVENT_JUMP_2_FIRST_WITHDRAW, null)*/
//NotifyMan.instance().sendEvent(VididinEvents.EVENT_JUMP_2_FIRST_WITHDRAW, null)*/
dismiss()
}
}
@ -37,9 +34,7 @@ class BeginnerGiftDialog(activity: Activity) : BindingDialog<DialogBeginnerGiftB
}
}
private fun gotoWatchVideo() {
Router.Withdraw.startActivity(mActivity)
}

View File

@ -4,7 +4,9 @@ import android.Manifest
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.core.graphics.toColorInt
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
@ -14,6 +16,7 @@ import com.ama.core.architecture.appBase.AppViewsFragment
import com.ama.core.architecture.appBase.OnFragmentBackgroundListener
import com.ama.core.architecture.util.AndroidUtil
import com.ama.core.architecture.util.ResUtil
import com.ama.core.architecture.util.ResUtil.dp
import com.ama.core.architecture.util.eventbus.NotifyMan
import com.ama.core.architecture.util.permission.PermissionUtil
import com.ama.core.architecture.util.setOnClickBatch
@ -33,6 +36,11 @@ import com.gamedog.vididin.manager.TaskManager
import com.gamedog.vididin.manager.taskbeans.BaseTaskState.Companion.STATE_FINISH
import com.gamedog.vididin.manager.taskbeans.BaseTaskState.Companion.STATE_ONGOING
import com.gamedog.vididin.router.Router
import com.ama.core.architecture.highlightpro.HighlightPro
import com.ama.core.architecture.highlightpro.parameter.Constraints
import com.ama.core.architecture.highlightpro.parameter.HighlightParameter
import com.ama.core.architecture.highlightpro.parameter.MarginOffset
import com.ama.core.architecture.highlightpro.shape.RectShape
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.launch
@ -136,6 +144,10 @@ class TasksFragment : AppViewsFragment<ViewBinding, UiState, ViewModel>(), OnTab
lifecycleScope.launch {
mTaskConfig = TaskManager.instance().getTaskConfig()
}
binding?.llTaskGame?.postDelayed({
showGuide()
}, 50)
}
private fun addDailySubTasks() {
@ -422,4 +434,39 @@ class TasksFragment : AppViewsFragment<ViewBinding, UiState, ViewModel>(), OnTab
}
private fun showGuide() {
HighlightPro.with(this@TasksFragment)
.setHighlightParameter {
HighlightParameter.Builder()
.setHighlightView(binding!!.goldContainer)
.setTipsViewId(R.layout.guide_step_gold)
.setHighlightShape(RectShape(10.dp, 10.dp, 10.dp))
.setHighlightHorizontalPadding(0.dp)
.setConstraints(Constraints.TopToBottomOfHighlight + Constraints.EndToEndOfHighlight)
.setMarginOffset(MarginOffset(top = -20.dp.toInt()))
.build()
}
.setHighlightParameter {
HighlightParameter.Builder()
.setHighlightView(binding!!.cashContainer)
.setTipsViewId(R.layout.guide_step_cash)
.setHighlightShape(RectShape(10.dp, 10.dp, 10.dp))
.setHighlightHorizontalPadding(0.dp)
.setConstraints(Constraints.TopToBottomOfHighlight + Constraints.EndToEndOfHighlight)
.setMarginOffset(MarginOffset(top = -20.dp.toInt()))
.build()
}
.setBackgroundColor("#cc000000".toColorInt())
.setOnShowCallback { index ->
}
.setOnDismissCallback {
}
.interceptBackPressed(true)
.show()
}
}

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/tv_tips"
android:layout_width="wrap_content"
android:layout_gravity="right"
android:layout_marginLeft="100dp"
android:layout_height="70dp"
android:src="@mipmap/guide_finger_b" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="70dp"
android:padding="15dp"
android:background="@mipmap/guide_bg_right">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_gravity="center"
android:textSize="16sp"
android:textColor="@color/gray3"
android:textStyle="bold"
android:text="@string/guide_hint_cash"
/>
</FrameLayout>
</FrameLayout>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/tv_tips"
android:layout_width="wrap_content"
android:layout_gravity="left"
android:layout_marginLeft="100dp"
android:layout_height="70dp"
android:src="@mipmap/guide_finger_b" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="70dp"
android:padding="15dp"
android:background="@mipmap/guide_bg_left">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_gravity="center"
android:textSize="16sp"
android:textColor="@color/gray3"
android:textStyle="bold"
android:text="@string/guide_hint_gold"
/>
</FrameLayout>
</FrameLayout>

View File

@ -32,138 +32,147 @@
android:orientation="horizontal"
android:layout_marginTop="15dp">
<LinearLayout
<FrameLayout
android:id="@+id/gold_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:background="@mipmap/task_bg_glod"
android:padding="10dp"
>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="14sp"
android:textColor="@color/pink_0b"
android:text="Centro de Missões"
/>
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_gravity="center_vertical"
android:layout_marginTop="10dp">
<androidx.appcompat.widget.AppCompatImageView
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@mipmap/task_bg_glod"
android:padding="10dp"
>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="21sp"
android:textSize="14sp"
android:textColor="@color/pink_0b"
android:src="@mipmap/home_gold"
android:text="Centro de Missões"
/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_gold_total"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="21sp"
android:textColor="@color/pink_0b"
android:layout_marginLeft="6dp"
android:text="0"
/>
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_gravity="center_vertical"
android:layout_marginTop="10dp">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="21sp"
android:textColor="@color/pink_0b"
android:src="@mipmap/home_gold"
/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_gold_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="21sp"
android:textColor="@color/pink_0b"
android:layout_marginLeft="6dp"
android:text="0"
/>
</LinearLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_watch_video_for_convert_gold_to_cash"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="8dp"
android:layout_marginTop="12dp"
android:drawableLeft="@mipmap/task_video"
android:drawablePadding="10dp"
android:gravity="center"
android:text="Resgatar"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout>
</FrameLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_watch_video_for_convert_gold_to_cash"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="8dp"
android:layout_marginTop="12dp"
android:drawableLeft="@mipmap/task_video"
android:drawablePadding="10dp"
android:gravity="center"
android:text="Resgatar"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
<FrameLayout
android:id="@+id/cash_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:background="@mipmap/task_bg_cash"
android:padding="10dp"
android:layout_marginLeft="10dp"
>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="14sp"
android:textColor="@color/green_39"
android:text="Meu Dinheiro"
/>
android:layout_marginLeft="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="10dp">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="21sp"
android:textColor="@color/pink_0b"
android:src="@mipmap/task_cash"
/>
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@mipmap/task_bg_cash"
android:padding="10dp"
>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_cash_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="21sp"
android:textSize="14sp"
android:textColor="@color/green_39"
android:layout_marginLeft="6dp"
android:text="R$ 0"
android:text="Meu Dinheiro"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="10dp">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="21sp"
android:textColor="@color/pink_0b"
android:src="@mipmap/task_cash"
/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_cash_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="21sp"
android:textColor="@color/green_39"
android:layout_marginLeft="6dp"
android:text="R$ 0"
/>
</LinearLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_withdraw"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="15sp"
android:textColor="@color/white"
android:layout_marginLeft="8dp"
android:layout_marginTop="12dp"
android:text="Sacar"
android:layout_gravity="center_horizontal"
android:gravity="center"
/>
</LinearLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_withdraw"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="15sp"
android:textColor="@color/white"
android:layout_marginLeft="8dp"
android:layout_marginTop="12dp"
android:text="Sacar"
android:layout_gravity="center_horizontal"
android:gravity="center"
/>
</LinearLayout>
</FrameLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -492,8 +501,6 @@
</LinearLayout>
</LinearLayout>
</com.ama.core.architecture.widget.MyScrollView>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -149,5 +149,7 @@
<string name="cost_gold">Consumo de moedas</string>
<string name="percent">%</string>
<string name="pix2_hint">Vincular conta para saque</string>
<string name="guide_hint_gold">Clique no botão para converter todas \nas “Moedas” em “Dinheiro”.</string>
<string name="guide_hint_cash">Clique aqui para ir para o saque.</string>
</resources>

View File

@ -0,0 +1,138 @@
package com.ama.core.architecture.highlightpro
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.Fragment
import com.ama.core.architecture.highlightpro.parameter.HighlightParameter
import com.ama.core.architecture.highlightpro.view.MaskContainer
class HighlightPro : HighlightViewInteractiveAction {
private val highlightProImpl: HighlightProImpl
private constructor(activity: Activity) {
highlightProImpl = HighlightProImpl(activity)
}
private constructor(fragment: Fragment) {
highlightProImpl = HighlightProImpl(fragment)
}
private constructor(view: ViewGroup) {
highlightProImpl = HighlightProImpl(view)
}
/**
* [show] is a final method to call then show a guide or a popup window
*/
override fun show() {
highlightProImpl.show()
}
/**
* Dismiss [HighlightPro] auto in common.
* we can manually call this method([dismiss]) to dismiss [HighlightPro]
*/
override fun dismiss() {
highlightProImpl.dismiss()
}
/**
* Set your mask background [color] of [MaskContainer]
*/
fun setBackgroundColor(color: Int): HighlightPro {
highlightProImpl.setBackgroundColor(color)
return this
}
/**
* Set a List of [HighlightParameter] so we can show more Highlight at once
*/
fun setHighlightParameters(highlightParameters: List<HighlightParameter>): HighlightPro {
highlightProImpl.setGuideViewParameters(highlightParameters)
return this
}
/**
* Set single [HighlightParameter]
*
* With [setHighlightParameter] we can use kotlin function with return
*/
fun setHighlightParameter(block: ()-> HighlightParameter): HighlightPro {
highlightProImpl.setGuideViewParameter(block = block)
return this
}
/**
* [showCallback] is a function which will be invoked when the [HighlightPro] show
*/
fun setOnShowCallback(showCallback: (Int) -> Unit) : HighlightPro{
highlightProImpl.setOnShowCallback(showCallback = showCallback)
return this
}
/**
* [dismissCallback] is a function which will be invoked when the [HighlightPro] dismiss
*/
fun setOnDismissCallback(dismissCallback: () -> Unit) : HighlightPro{
highlightProImpl.setOnDismissCallback(dismissCallback)
return this
}
/**
* [clickCallback] is a callback when user clicked any place in [MaskContainer]
*/
fun setOnMaskViewClickCallback(clickCallback: (View) -> Unit): HighlightPro {
highlightProImpl.setOnGuideViewClickCallback(clickCallback)
return this
}
/**
* [enableHighlight] give you access to make Highlight dismiss and show UI like popup window
*/
fun enableHighlight(enableHighlight: Boolean): HighlightPro {
highlightProImpl.enableHighlight(enableHighlight)
return this
}
/**
* [interceptBackPressed] is [true] will intercept activity onBackPressed and [HighlightPro] will dismiss
*/
fun interceptBackPressed(interceptBackPressed: Boolean): HighlightPro {
highlightProImpl.interceptBackPressed(interceptBackPressed)
return this
}
fun needAnchorTipView(needAnchorTipView: Boolean) : HighlightPro{
highlightProImpl.needAnchorTipView(needAnchorTipView)
return this
}
companion object {
/**
* DecorView of [activity] treat as the rootView
*/
fun with(activity: Activity): HighlightPro {
return HighlightPro(activity)
}
/**
* DecorView of [fragment]'s [activity] treat as the rootView
*/
fun with(fragment: Fragment): HighlightPro {
return HighlightPro(fragment)
}
/**
* the [container] treat as rootView and the container should be a FrameLayout or a viewGroup extends FrameLayout
* to ensure the UI can display normally
*/
fun with(container: FrameLayout): HighlightPro {
return HighlightPro(container)
}
}
}

View File

@ -0,0 +1,226 @@
package com.ama.core.architecture.highlightpro
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.doOnPreDraw
import androidx.fragment.app.Fragment
import com.ama.core.architecture.highlightpro.parameter.HighlightParameter
import com.ama.core.architecture.highlightpro.shape.RectShape
import com.ama.core.architecture.highlightpro.util.calculateHighLightViewRect
import com.ama.core.architecture.highlightpro.util.dp
import com.ama.core.architecture.highlightpro.util.isAttachToWindow
import com.ama.core.architecture.highlightpro.view.MaskContainer
internal class HighlightProImpl : HighlightViewInteractiveAction {
companion object {
const val TAG = "HYY-GuideProImpl"
}
private var isFragmentRoot: Boolean = false
private var fragmentRootView: View? = null
private var curIndex: Int = 0
private val highlightParameters: MutableList<List<HighlightParameter>> = mutableListOf()
private var hasShow: Boolean = false
private val rootView: ViewGroup
private val maskContainer: MaskContainer
private var released = false
private var showCallback: ((index: Int) -> Unit)? = null
private var dismissCallback: (() -> Unit)? = null
private var clickCallback: ((View) -> Unit)? = null
private var autoNext = true
private var needAnchorTipView = true
// private var
private val onClickListener = View.OnClickListener {
clickCallback?.invoke(it)
if (autoNext) {
showNextHighLightView()
}
}
internal constructor(activity: Activity) {
rootView = activity.window.decorView as ViewGroup
maskContainer = MaskContainer(activity)
}
internal constructor(view: ViewGroup) {
rootView = view
maskContainer = MaskContainer(view.context)
}
internal constructor(fragment: Fragment) {
if (fragment.view == null)
throw IllegalStateException("The fragment's view not created yet,please call this after fragment's onViewCreated()")
if (fragment.isDetached)
throw IllegalStateException("The fragment have detached. It is not attach to an activity!")
rootView = fragment.requireActivity().window.decorView as ViewGroup
fragmentRootView = fragment.view
isFragmentRoot = true
maskContainer = MaskContainer(rootView.context)
}
override fun show() {
if (released) return
println("$TAG show")
//todo give user access to intercept click event
// if (!intercept) {
maskContainer.setOnClickListener(onClickListener)
// }
//if constructor's param is activity or view we care about rootView's attachedToWindow
//if constructor's param is fragment we care about [fragmentRootView]'s width is not 0
if ((isFragmentRoot.not() && rootView.isAttachToWindow()) ||
(isFragmentRoot && fragmentRootView?.width != 0)
) {
if (maskContainer.parent == null) {
//add guideViewContainer to rootView
rootView.addView(
maskContainer,
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
)
if (maskContainer.interceptBackPressed) {
maskContainer.apply {
//three line below enable maskContainer focusable and got focused
isFocusable = true
isFocusableInTouchMode = true
requestFocus()
setOnBackPressedCallback {
showNextHighLightView()
}
}
}
showNextHighLightView()
}
} else {
if (isFragmentRoot) {
fragmentRootView?.doOnPreDraw {
println("$TAG fragmentRootView pre draw")
//ensure this method will be call once
if (hasShow) return@doOnPreDraw
hasShow = false
show()
}
} else {
rootView.doOnPreDraw {
//ensure this method will be call once
if (hasShow) return@doOnPreDraw
hasShow = false
show()
}
}
}
}
/**
* this is the function which real show highLightView rect and tipView
*/
private fun showNextHighLightView() {
if (released) return
println("$TAG showNextHighLightView")
if (hasHighLightView().not()) {
dismiss()
} else {
highlightParameters[0].forEach {
checkOrInitParameter(it)
}
showCallback?.invoke(curIndex)
curIndex++
maskContainer.setRootWidth(rootView.width-rootView.paddingLeft-rootView.paddingRight)//ignore padding
maskContainer.setRootHeight(rootView.height-rootView.paddingTop-rootView.paddingBottom)//ignore padding
maskContainer.setHighLightParameters(highlightParameters[0])
highlightParameters.removeAt(0)
}
}
/**
* check valid for each parameter if invalid set default value
*/
private fun checkOrInitParameter(parameter: HighlightParameter) {
if (parameter.highLightView == null) {
parameter.highLightView = rootView.findViewById(parameter.highLightViewId)
}
if (parameter.tipsView == null && checkTipViewIdIsValid(parameter)) {
parameter.tipsView = LayoutInflater.from(maskContainer.context)
.inflate(parameter.tipsViewId, maskContainer, false)
}
if (parameter.highlightShape == null) {
parameter.highlightShape = RectShape(2f.dp, 2f.dp, 2f.dp)
}
parameter.calculateHighLightViewRect(rootView)
}
private fun checkTipViewIdIsValid(parameter: HighlightParameter): Boolean = parameter.tipsViewId != -1
private fun hasHighLightView(): Boolean = highlightParameters.isNotEmpty()
override fun dismiss() {
if (released) return
//release every thing
released = true
//todo if we want have a dismiss animation these code need rewrite
maskContainer.isFocusable = false
maskContainer.clearFocus()
rootView.removeView(maskContainer)
maskContainer.removeAllViews()
dismissCallback?.invoke()
}
fun setBackgroundColor(color: Int) {
maskContainer.setBackgroundColor(color)
}
fun setGuideViewParameters(highlightParameters: List<HighlightParameter>) {
if (released) return
this.highlightParameters.add(highlightParameters)
}
fun setGuideViewParameter(block: () -> HighlightParameter) {
if (released) return
highlightParameters.add(listOf(block.invoke()))
}
fun setOnShowCallback(showCallback: (Int) -> Unit) {
this.showCallback = showCallback
}
fun setOnDismissCallback(dismissCallback: () -> Unit) {
this.dismissCallback = dismissCallback
}
fun setOnGuideViewClickCallback(clickCallback: (View) -> Unit) {
this.clickCallback = clickCallback
}
fun enableHighlight(enableHighlight: Boolean) {
this.maskContainer.enableHighlight = enableHighlight
}
fun interceptBackPressed(interceptBackPressed: Boolean) {
this.maskContainer.interceptBackPressed = interceptBackPressed
}
fun needAnchorTipView(needAnchorTipView: Boolean) {
this.needAnchorTipView = needAnchorTipView
this.maskContainer.needAnchorTipView = needAnchorTipView
}
}

View File

@ -0,0 +1,16 @@
package com.ama.core.architecture.highlightpro
interface HighlightViewInteractiveAction {
/**
* show [HighlightPro]
*/
fun show()
/**
* dismiss [HighlightPro]
*/
fun dismiss()
}

View File

@ -0,0 +1,22 @@
package com.ama.core.architecture.highlightpro.parameter
sealed class Constraints {
//vertical constraints
object TopToTopOfHighlight : Constraints()
object BottomToTopOfHighlight : Constraints()
object BottomToBottomOfHighlight : Constraints()
object TopToBottomOfHighlight : Constraints()
//horizontal constraints
object StartToStartOfHighlight : Constraints()
object StartToEndOfHighlight : Constraints()
object EndToEndOfHighlight : Constraints()
object EndToStartOfHighlight : Constraints()
//center constraints
object CenterHorizontalOfHighlight : Constraints()
object CenterVerticalOfHighlight : Constraints()
operator fun plus(locationGravity: Constraints): List<Constraints> {
return listOf(this, locationGravity)
}
}

View File

@ -0,0 +1,153 @@
package com.ama.core.architecture.highlightpro.parameter
import android.graphics.RectF
import android.view.View
import android.view.animation.Animation
import com.ama.core.architecture.highlightpro.shape.HighlightShape
class HighlightParameter {
internal var highLightViewId: Int = -1
internal var highLightView: View? = null
internal var tipsViewId: Int = -1
internal var tipsView: View? = null
internal var highlightShape: HighlightShape? = null
internal var rect: RectF = RectF()
internal var horizontalPadding: Float = 0f
internal var verticalPadding: Float = 0f
internal var marginOffset: MarginOffset = MarginOffset()
internal var offsetX = 0
internal var offsetY = 0
internal val constraints =
mutableListOf(Constraints.TopToBottomOfHighlight, Constraints.StartToStartOfHighlight)
internal var tipViewDisplayAnimation: Animation? = null
fun setHighLightViewId(viewId: Int) {
this.highLightViewId = viewId
}
fun setHighLightView(highLightView: View) {
this.highLightView = highLightView
}
operator fun plus(highlightParameter: HighlightParameter): List<HighlightParameter> {
return listOf(this, highlightParameter)
}
class Builder {
private val highlightParameter: HighlightParameter = HighlightParameter()
/**
* [viewId] is the highLightView's id
* it will be override by [setHighlightView].
*/
fun setHighlightViewId(viewId: Int): Builder {
highlightParameter.highLightViewId = viewId
return this
}
/**
* [highLightView] is the view which you want to make it highLight
* And it will make [setHighlightViewId] useless.
*/
fun setHighlightView(highLightView: View): Builder {
highlightParameter.highLightView = highLightView
return this
}
/**
* [viewId] is the tipsView's id
* it will be override by [setTipsView].
*/
fun setTipsViewId(viewId: Int): Builder {
highlightParameter.tipsViewId = viewId
return this
}
/**
* [tipsView] is the view which you want to add it on [GuideViewContainer]
* And it will make [setTipsViewId] useless.
*/
fun setTipsView(tipsView: View): Builder {
highlightParameter.tipsView = tipsView
return this
}
/**
* [highlightShape] is the highlight out rect shape and you can
* custom your shape reference our shapes exit.
*/
fun setHighlightShape(highlightShape: HighlightShape): Builder {
highlightParameter.highlightShape = highlightShape
return this
}
/**
* [padding] is the vertical dimension padding use by expand the rect area of
* highlight.
*/
fun setHighlightVerticalPadding(padding: Float): Builder {
highlightParameter.verticalPadding = padding
return this
}
/**
* [marginOffset] is the extra dimension use by position highlight rect.
*/
fun setMarginOffset(marginOffset: MarginOffset): Builder {
highlightParameter.marginOffset = marginOffset
return this
}
/**
* [constraints] is a list of constraints provides some constraints of highlight
* rect position.
*/
fun setConstraints(constraints: List<Constraints>): Builder {
highlightParameter.constraints.clear()
highlightParameter.constraints.addAll(constraints)
return this
}
/**
* [padding] is the horizontal dimension padding use by expand the rect area of
* highlight.
*/
fun setHighlightHorizontalPadding(padding: Float): Builder {
highlightParameter.horizontalPadding = padding
return this
}
/**
* [tipViewDisplayAnimation] is the animation to display for Highlight tips view
*/
fun setTipViewDisplayAnimation(tipViewDisplayAnimation: Animation?): Builder {
highlightParameter.tipViewDisplayAnimation = tipViewDisplayAnimation
return this
}
fun offsetX(offsetX: Int): Builder {
highlightParameter.offsetX = offsetX
return this
}
fun offsetY(offsetY: Int): Builder {
highlightParameter.offsetY = offsetY
return this
}
fun build(): HighlightParameter = highlightParameter
}
}

View File

@ -0,0 +1,9 @@
package com.ama.core.architecture.highlightpro.parameter
class MarginOffset(
val start: Int = 0,
val top: Int = 0,
val end: Int = 0,
val bottom: Int = 0
)

View File

@ -0,0 +1,17 @@
package com.ama.core.architecture.highlightpro.shape
import android.graphics.Path
import android.graphics.RectF
import kotlin.math.max
class CircleShape(private val radius: Float = 0f) : HighlightShape(radius) {
override fun initRect(rectF: RectF) {
super.initRect(rectF)
rect?.run {
path.reset()
path.addCircle((left+right)/2,(top+bottom)/2, max(height(),width()) /2,Path.Direction.CW)
}
}
}

View File

@ -0,0 +1,50 @@
package com.ama.core.architecture.highlightpro.shape
import android.graphics.*
open class HighlightShape(val blurRadius: Float = 0.0f) {
private lateinit var paint: Paint
//clip path
internal val path by lazy {
Path()
}
init {
paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
isDither = true
color = Color.WHITE
}
//paint blur style
if (blurRadius > 0) {
paint.maskFilter = BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.SOLID)
}
}
protected var rect: RectF? = null
/**
* init rect child should override initRect and init path
* */
open fun initRect(rectF: RectF) {
this.rect = rectF
}
/**
* draw our path
*/
fun drawPath(canvas: Canvas) {
rect?.run {
if (isEmpty.not()) {
canvas.drawPath(path, paint)
}
}
}
fun setPaint(paint: Paint) {
this.paint = paint
}
}

View File

@ -0,0 +1,16 @@
package com.ama.core.architecture.highlightpro.shape
import android.graphics.Path
import android.graphics.RectF
class OvalShape(private val radius: Float = 0f) : HighlightShape(radius) {
override fun initRect(rectF: RectF) {
super.initRect(rectF)
rect?.run {
path.reset()
path.addOval(this,Path.Direction.CW)
}
}
}

View File

@ -0,0 +1,16 @@
package com.ama.core.architecture.highlightpro.shape
import android.graphics.Path
import android.graphics.RectF
class RectShape(private val rx: Float = 0f, private val ry: Float = 0f, private val radius: Float = 0f) : HighlightShape(radius) {
override fun initRect(rectF: RectF) {
super.initRect(rectF)
rect?.run {
path.reset()
path.addRoundRect(this, rx, ry, Path.Direction.CW)
}
}
}

View File

@ -0,0 +1,88 @@
package com.ama.core.architecture.highlightpro.util
import android.content.res.Resources
import android.graphics.RectF
import android.os.Build
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import com.ama.core.architecture.highlightpro.parameter.HighlightParameter
fun View.isAttachToWindow(): Boolean {
return if (Build.VERSION.SDK_INT >= 19) {
isAttachedToWindow
} else {
windowToken != null
}
}
fun View?.getRectOnScreen(): RectF {
if (this == null) {
return RectF()
}
val pos = intArrayOf(0, 0)
getLocationOnScreen(pos)
return RectF().apply {
left = pos[0].toFloat()
top = pos[1].toFloat()
right = pos[0].toFloat() + width
bottom = pos[1].toFloat() + height
}
}
fun HighlightParameter.calculateHighLightViewRect(rootView: ViewGroup) {
if (highLightView == null) return
val rectOnScreen = highLightView.getRectOnScreen()
//consider rootView's position
val rootViewPos = IntArray(2)
rootView.getLocationOnScreen(rootViewPos)
rectOnScreen.left -= rootViewPos[0]
rectOnScreen.right -= rootViewPos[0]
rectOnScreen.top -= rootViewPos[1]
rectOnScreen.bottom -= rootViewPos[1]
rectOnScreen.left -= rootView.paddingLeft.toFloat()
rectOnScreen.right -= rootView.paddingLeft.toFloat()
rectOnScreen.top -= rootView.paddingTop.toFloat()
rectOnScreen.bottom -= rootView.paddingTop.toFloat()
rect = rectOnScreen
rect.run {
left -= horizontalPadding
top -= verticalPadding
right += horizontalPadding
bottom += verticalPadding
highlightShape?.initRect(this)
}
}
val Float.dp
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this,
Resources.getSystem().displayMetrics
)
val Float.sp
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
this,
Resources.getSystem().displayMetrics
)
val Int.dp
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
val Int.sp
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
this.toFloat(),
Resources.getSystem().displayMetrics
).toInt()

View File

@ -0,0 +1,262 @@
package com.ama.core.architecture.highlightpro.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Region
import android.util.AttributeSet
import android.view.Gravity
import android.view.KeyEvent
import android.view.View
import android.widget.FrameLayout
import androidx.core.graphics.toColorInt
import androidx.core.view.children
import androidx.core.view.doOnPreDraw
import com.ama.core.architecture.highlightpro.parameter.Constraints
import com.ama.core.architecture.highlightpro.parameter.HighlightParameter
internal class MaskContainer constructor(context: Context, attributeSet: AttributeSet? = null) :
FrameLayout(context, attributeSet) {
companion object {
const val TAG = "MaskContainer"
}
private var rootWidth: Int = 0
private var rootHeight: Int = 0
private var bgColor: Int = -1
private val highLightViewParameters = mutableListOf<HighlightParameter>()
private val defaultHighlightBgColor: Int
get() = "#80000000".toColorInt()
private val defaultBgColor: Int
get() = "#00000000".toColorInt()
private var backPressedCallback: (() -> Unit)? = null
internal var enableHighlight = true
internal var interceptBackPressed = false
internal var needAnchorTipView = true
init {
setWillNotDraw(false)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (enableHighlight) {
canvas.save()
//clip rect
highLightViewParameters.forEach { parameter ->
parameter.highlightShape?.run {
canvas.clipPath(path, Region.Op.DIFFERENCE)
}
}
if (bgColor == -1) {
bgColor = defaultHighlightBgColor
}
canvas.drawColor(bgColor)
highLightViewParameters.forEach { parameter ->
parameter.highlightShape?.run {
drawPath(canvas)
}
}
canvas.restore()
} else {
if (bgColor == -1) {
bgColor = defaultBgColor
}
canvas.drawColor(bgColor)
}
}
override fun setBackgroundColor(color: Int) {
// super.setBackgroundColor(color)
bgColor = color
}
fun setOnBackPressedCallback(block: () -> Unit) {
backPressedCallback = block
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
backPressedCallback?.invoke()
return interceptBackPressed
}
return super.onKeyDown(keyCode, event)
}
fun setHighLightParameters(list: List<HighlightParameter>) {
children.forEach {
it.clearAnimation()
}
//clear all childView
removeAllViews()
highLightViewParameters.clear()
highLightViewParameters.addAll(list)
addTipsView()
}
private fun addTipsView() {
if (needAnchorTipView) {
highLightViewParameters.forEach { highLightViewParameters ->
highLightViewParameters.tipsView?.run {
val layoutParams = calculateTipsViewLayoutParams(this, highLightViewParameters)
if (highLightViewParameters.tipViewDisplayAnimation != null) {
startAnimation(highLightViewParameters.tipViewDisplayAnimation)
}
addView(this, layoutParams)
highLightViewParameters.tipsView?.doOnPreDraw {
}
}
}
} else {
highLightViewParameters.forEach { highLightViewParameters ->
highLightViewParameters.tipsView?.run {
var layoutParams = (this.layoutParams ?: LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
)) as LayoutParams
layoutParams.topMargin = highLightViewParameters.offsetY
layoutParams.leftMargin = highLightViewParameters.offsetX
if (highLightViewParameters.tipViewDisplayAnimation != null) {
startAnimation(highLightViewParameters.tipViewDisplayAnimation)
}
addView(this, layoutParams)
}
}
}
}
private fun calculateTipsViewLayoutParams(
view: View,
parameter: HighlightParameter
): LayoutParams {
var layoutParams = (view.layoutParams ?: LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
)) as LayoutParams
println("$TAG calculateTipsViewLayoutParams tipsView layoutParams--> $layoutParams")
val margin = parameter.marginOffset
val highLightRect = parameter.rect
val gravities = mutableListOf<Int>()
parameter.constraints.forEach {
when (it) {
Constraints.StartToStartOfHighlight -> {
layoutParams.leftMargin = (highLightRect.left + margin.start).toInt()
gravities.add(Gravity.START)
}
Constraints.EndToStartOfHighlight -> {
//if constraint start to end of Highlight we need set right rightMargin
//and if margin.end is not 0 we should add the margin end
layoutParams.rightMargin =
(rootWidth - highLightRect.right + highLightRect.width() + margin.end).toInt()
gravities.add(Gravity.END)
}
Constraints.StartToEndOfHighlight -> {
layoutParams.leftMargin = (highLightRect.right + margin.start).toInt()
gravities.add(Gravity.START)
}
Constraints.EndToEndOfHighlight -> {
layoutParams.rightMargin =
(rootWidth - highLightRect.right + margin.end).toInt()
gravities.add(Gravity.END)
}
Constraints.TopToTopOfHighlight -> {
layoutParams.topMargin = (highLightRect.top + margin.top).toInt()
gravities.add(Gravity.TOP)
}
Constraints.BottomToBottomOfHighlight -> {
layoutParams.bottomMargin =
(rootHeight - highLightRect.bottom + margin.bottom).toInt()
gravities.add(Gravity.BOTTOM)
}
Constraints.BottomToTopOfHighlight -> {
layoutParams.bottomMargin =
(rootHeight - highLightRect.bottom + highLightRect.height() + margin.bottom).toInt()
gravities.add(Gravity.BOTTOM)
}
Constraints.TopToBottomOfHighlight -> {
layoutParams.topMargin = (highLightRect.bottom + margin.top).toInt()
gravities.add(Gravity.TOP)
}
Constraints.CenterHorizontalOfHighlight -> {
val width = layoutParams.width
if (width <= 0) {
layoutParams.leftMargin =
(highLightRect.left + highLightRect.width() / 2f).toInt()
gravities.add(Gravity.START)
view.doOnPreDraw { tipsView ->
layoutParams.leftMargin =
(highLightRect.left + highLightRect.width() / 2f - tipsView.width / 2f).toInt()
view.layoutParams = layoutParams
}
} else {
layoutParams.leftMargin =
(highLightRect.left + highLightRect.width() / 2f - width / 2f).toInt()
gravities.add(Gravity.START)
}
}
Constraints.CenterVerticalOfHighlight -> {
val height = layoutParams.height
if (height <= 0) {
layoutParams.topMargin =
(highLightRect.top + highLightRect.height() / 2f).toInt()
gravities.add(Gravity.TOP)
view.doOnPreDraw { tipsView ->
layoutParams.topMargin =
(highLightRect.top + highLightRect.height() / 2f - tipsView.height / 2f).toInt()
view.layoutParams = layoutParams
}
} else {
layoutParams.topMargin =
(highLightRect.top + highLightRect.height() / 2f - height / 2f).toInt()
gravities.add(Gravity.TOP)
}
}
}
}
gravities.forEachIndexed { index, gravity ->
if (index == 0) layoutParams.gravity = gravity
else layoutParams.gravity = layoutParams.gravity or gravity
}
return layoutParams
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
highLightViewParameters.clear()
}
fun setRootWidth(width: Int) {
this.rootWidth = width
}
fun setRootHeight(height: Int) {
this.rootHeight = height
}
}