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.okhttp.logging)
implementation(libs.retrofit) implementation(libs.retrofit)
implementation(libs.retrofit.kotlin.serialization) implementation(libs.retrofit.kotlin.serialization)
//implementation 'com.github.hyy920109:GuidePro:1.0.3'
} }

View File

@ -1,12 +1,9 @@
package com.gamedog.vididin.main package com.gamedog.vididin.main
import android.app.Activity import android.app.Activity
import com.ama.core.architecture.util.eventbus.NotifyMan
import com.ama.core.architecture.util.setOnClickBatch import com.ama.core.architecture.util.setOnClickBatch
import com.ama.core.architecture.widget.BindingDialog import com.ama.core.architecture.widget.BindingDialog
import com.gamedog.vididin.VididinEvents
import com.vididin.real.money.game.databinding.DialogBeginnerGiftBinding import com.vididin.real.money.game.databinding.DialogBeginnerGiftBinding
import com.gamedog.vididin.router.Router
class BeginnerGiftDialog(activity: Activity) : BindingDialog<DialogBeginnerGiftBinding>(activity, DialogBeginnerGiftBinding::inflate) { class BeginnerGiftDialog(activity: Activity) : BindingDialog<DialogBeginnerGiftBinding>(activity, DialogBeginnerGiftBinding::inflate) {
@ -26,10 +23,10 @@ class BeginnerGiftDialog(activity: Activity) : BindingDialog<DialogBeginnerGiftB
setOnClickBatch(tvAction) { setOnClickBatch(tvAction) {
when (this) { when (this) {
tvAction -> { tvAction -> {
/*if (mActivity is MainActivity) { if (mActivity is MainActivity) {
(mActivity as MainActivity).switchTab(1) (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() 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.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.core.graphics.toColorInt
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding 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.appBase.OnFragmentBackgroundListener
import com.ama.core.architecture.util.AndroidUtil import com.ama.core.architecture.util.AndroidUtil
import com.ama.core.architecture.util.ResUtil 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.eventbus.NotifyMan
import com.ama.core.architecture.util.permission.PermissionUtil import com.ama.core.architecture.util.permission.PermissionUtil
import com.ama.core.architecture.util.setOnClickBatch 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_FINISH
import com.gamedog.vididin.manager.taskbeans.BaseTaskState.Companion.STATE_ONGOING import com.gamedog.vididin.manager.taskbeans.BaseTaskState.Companion.STATE_ONGOING
import com.gamedog.vididin.router.Router 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 dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Runnable import kotlinx.coroutines.Runnable
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -136,6 +144,10 @@ class TasksFragment : AppViewsFragment<ViewBinding, UiState, ViewModel>(), OnTab
lifecycleScope.launch { lifecycleScope.launch {
mTaskConfig = TaskManager.instance().getTaskConfig() mTaskConfig = TaskManager.instance().getTaskConfig()
} }
binding?.llTaskGame?.postDelayed({
showGuide()
}, 50)
} }
private fun addDailySubTasks() { 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:orientation="horizontal"
android:layout_marginTop="15dp"> android:layout_marginTop="15dp">
<LinearLayout <FrameLayout
android:id="@+id/gold_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" 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"
/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:orientation="horizontal" android:orientation="vertical"
android:gravity="center_vertical" android:background="@mipmap/task_bg_glod"
android:layout_gravity="center_vertical" android:padding="10dp"
android:layout_marginTop="10dp"> >
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textStyle="bold" android:textStyle="bold"
android:textSize="21sp" android:textSize="14sp"
android:textColor="@color/pink_0b" android:textColor="@color/pink_0b"
android:src="@mipmap/home_gold" android:text="Centro de Missões"
/> />
<androidx.appcompat.widget.AppCompatTextView <LinearLayout
android:id="@+id/tv_gold_total" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textStyle="bold" android:orientation="horizontal"
android:textSize="21sp" android:gravity="center_vertical"
android:textColor="@color/pink_0b" android:layout_gravity="center_vertical"
android:layout_marginLeft="6dp" android:layout_marginTop="10dp">
android:text="0" <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> </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> <FrameLayout
android:id="@+id/cash_container"
<LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"
android:orientation="vertical" android:layout_marginLeft="10dp">
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"
/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:orientation="horizontal" android:orientation="vertical"
android:gravity="center_vertical" android:background="@mipmap/task_bg_cash"
android:layout_marginTop="10dp"> android:padding="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 <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_cash_total"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textStyle="bold" android:textStyle="bold"
android:textSize="21sp" android:textSize="14sp"
android:textColor="@color/green_39" android:textColor="@color/green_39"
android:layout_marginLeft="6dp" android:text="Meu Dinheiro"
android:text="R$ 0"
/> />
<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> </LinearLayout>
</FrameLayout>
<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>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -492,8 +501,6 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</com.ama.core.architecture.widget.MyScrollView> </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="cost_gold">Consumo de moedas</string>
<string name="percent">%</string> <string name="percent">%</string>
<string name="pix2_hint">Vincular conta para saque</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> </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
}
}