手把手教写教程引导UI
教程引导UI是指用户第一次进入APP会教用户如何使用的一个功能
现在市场上比较出名的APP,基本都会有这个UI,特别游戏大多数都会有
很多人一遇到这种需求就头大,现在我一步一步教您学会该控件的分析与写法
分析教程引导UI思路
- 在布局上面加一个控件
- 计算针对对象所在位置控件高亮
- 在针对对象控件加入指引控件
- 调整指引控件所在位置,到这步就已经完成,至于其他属性可以根据项目需求自己去调整实现
如何在布局上面加一个控件
- 建立蒙层根控件
package com.icez.myapplication.weight.tutorials
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.ViewGroup
/**
* 蒙层根布局
*/
class TutorialsView :ViewGroup{
/**
* 蒙层范围
*/
var mOverlayRect: Rect ?= null
/**
* 蒙层画布
*/
var mOverlayCanvas:Canvas ?= null
/**
* 蒙层位图
*/
var mOverlayBitmap:Bitmap ?= null
var mOverlayPaint:Paint ?= null
constructor(context:Context):this(context,null)
constructor(context:Context,attrs: AttributeSet?):this(context,attrs,0)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int):super(context,attrs,defStyle){
init()
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//设置填充的位图颜色,0表示透明
mOverlayBitmap?.eraseColor(0)
//设置画布的颜色
mOverlayPaint?.color?.let { mOverlayCanvas?.drawColor(it) }
mOverlayRect?.left?.toFloat()?.let {
mOverlayRect?.top?.toFloat()?.let { it1 ->
mOverlayBitmap?.let { it2 ->
canvas?.drawBitmap(
it2,
it, it1,null)
}
}
}
}
fun setTutorialsColor(color:Int){
mOverlayPaint?.color = color
}
fun setTutorialsAlpha(alpha:Int){
mOverlayPaint?.alpha = alpha
}
fun init(){
//防止绘制出现问题
setWillNotDraw(false)
mOverlayRect = Rect()
val wh = TutorialsUtil.getWindowSize(context)
mOverlayRect?.set(0,0,wh[0],wh[1])
mOverlayBitmap = Bitmap.createBitmap(wh[0],wh[1],Bitmap.Config.ARGB_8888)
mOverlayCanvas = Canvas()
mOverlayCanvas?.setBitmap(mOverlayBitmap)
mOverlayPaint = Paint()
}
}
- 建立教程属性类
package com.icez.myapplication.weight.tutorials
import android.view.View
/**
* 教程属性
*/
data class TutorialsAttrs(
var mTargetView: View? = null,//针对控件
var mAlpha:Int = 150
)
- 建立教程子控件接口
package com.icez.myapplication.weight.tutorials
import android.view.View
/**
* 子控件
*/
interface TutorialsAdapter{
fun getView(): View
}
- 建立教程构建器
package com.icez.myapplication.weight.tutorials
import android.view.View
import android.view.ViewGroup
import java.lang.RuntimeException
/**
* 教程构建器,用于自定义蒙层控件的属性
*/
class TutorialsBuilder{
var mTutorialsAttrs:TutorialsAttrs ?= null
var mTutorialsAdapter:TutorialsAdapter ?= null
init {
mTutorialsAttrs = TutorialsAttrs()
}
/**
* 设置教程控件
*/
fun setTargetView(targetView: View):TutorialsBuilder{
if(targetView==null){
throw RuntimeException("Icez hint:targetView is not null. Please check it.")
}
mTutorialsAttrs?.mTargetView = targetView
return this
}
/**
* 设置蒙层透明度
*/
fun setAlpha(alpha:Int):TutorialsBuilder{
if(alpha<0){
throw RuntimeException("Icez hint:alpha cannot be less than zero. Please check it.")
}
mTutorialsAttrs?.mAlpha = alpha
return this
}
/**
* 设置蒙层子控件
*/
fun setTutorialsAdapter(tutorialsAdapter: TutorialsAdapter):TutorialsBuilder{
if(tutorialsAdapter==null){
throw RuntimeException("Icez hint:tutorialsAdapter is not null. Please check it.")
}
mTutorialsAdapter = tutorialsAdapter
return this
}
/**
* 创建蒙层教程属性
*/
fun createTutorials():Tutorials{
val mTutorials = Tutorials()
mTutorials.mTutorialsAttrs = mTutorialsAttrs
mTutorials.mTutorialsAdapter = mTutorialsAdapter
return mTutorials
}
}
- 创建教程管理类
package com.icez.myapplication.weight.tutorials
import android.app.Activity
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
/**
* 教程管理类
*/
class Tutorials : View.OnTouchListener {
var mTutorialsAdapter:TutorialsAdapter ?= null
var mTutorialsView: TutorialsView? = null
var mActivity:Activity ?= null
var mRootView:ViewGroup ?= null
var mTutorialsAttrs:TutorialsAttrs ?= null
/**
* 显示教程UI
*/
fun show(activity:Activity,rootView: ViewGroup?) {
mRootView = rootView
mActivity = activity
if (mRootView == null) {
mRootView = mActivity?.window?.decorView as ViewGroup
}
mTutorialsView = createTutorials(mActivity)
if (mRootView != null && mTutorialsView != null) {
mRootView?.addView(mTutorialsView)
}
}
/**
* 创建教程控件
*/
fun createTutorials(activity: Activity?): TutorialsView? {
val mTutorialsView: TutorialsView? = activity?.let { TutorialsView(it) }
mTutorialsView?.setTutorialsAlpha(mTutorialsAttrs?.mAlpha?:0)
mTutorialsView?.setOnTouchListener(this)
return mTutorialsView
}
/**
* 关闭教程UI
*/
fun dimiss(){
mTutorialsView?.removeAllViews()
mRootView?.removeView(mTutorialsView)
mTutorialsView = null
}
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
return true
}
}
- 工具类
package com.icez.myapplication.weight.tutorials
import android.content.Context
import android.util.DisplayMetrics
import android.view.WindowManager
/**
* 工具类
*/
class TutorialsUtil {
companion object{
/**
* 获取窗口的大小
*/
fun getWindowSize(context: Context): IntArray {
val wh = IntArray(2)
val wm:WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val displayMetrics:DisplayMetrics = DisplayMetrics()
wm.defaultDisplay.getRealMetrics(displayMetrics)
wh[0] = displayMetrics.widthPixels
wh[1] = displayMetrics.heightPixels
return wh
}
}
}
计算针对对象所在位置控件高亮
- 修改TutorialsBuilder类
package com.icez.myapplication.weight.tutorials
import android.view.View
import android.view.ViewGroup
import java.lang.RuntimeException
/**
* 教程构建器,用于自定义蒙层控件的属性
*/
class TutorialsBuilder{
var mTutorialsAttrs:TutorialsAttrs ?= null
var mTutorialsAdapter:TutorialsAdapter ?= null
init {
mTutorialsAttrs = TutorialsAttrs()
}
/**
* 设置教程控件
*/
fun setTargetView(targetView: View):TutorialsBuilder{
if(targetView==null){
throw RuntimeException("Icez hint:targetView is not null. Please check it.")
}
mTutorialsAttrs?.mTargetView = targetView
return this
}
/**
* 设置蒙层透明度
*/
fun setAlpha(alpha:Int):TutorialsBuilder{
if(alpha<0){
throw RuntimeException("Icez hint:alpha cannot be less than zero. Please check it.")
}
mTutorialsAttrs?.mAlpha = alpha
return this
}
/**
* 设置蒙层子控件
*/
fun setTutorialsAdapter(tutorialsAdapter: TutorialsAdapter):TutorialsBuilder{
if(tutorialsAdapter==null){
throw RuntimeException("Icez hint:tutorialsAdapter is not null. Please check it.")
}
mTutorialsAdapter = tutorialsAdapter
return this
}
/**
* 创建蒙层教程属性
*/
fun createTutorials():Tutorials{
val mTutorials = Tutorials()
mTutorials.mTutorialsAttrs = mTutorialsAttrs
mTutorials.mTutorialsAdapter = mTutorialsAdapter
return mTutorials
}
}
- 修改TutorialsView 类
package com.icez.myapplication.weight.tutorials
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.ViewGroup
/**
* 蒙层根布局
*/
class TutorialsView :ViewGroup{
var mTargetRectF:RectF ?= null
/**
* 蒙层范围
*/
var mOverlayRect: Rect ?= null
/**
* 蒙层画布
*/
var mOverlayCanvas:Canvas ?= null
/**
* 蒙层位图
*/
var mOverlayBitmap:Bitmap ?= null
var mOverlayPaint:Paint ?= null
var mTargetPaint:Paint ?= null
constructor(context:Context):this(context,null)
constructor(context:Context,attrs: AttributeSet?):this(context,attrs,0)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int):super(context,attrs,defStyle){
init()
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//设置填充的位图颜色,0表示透明
mOverlayBitmap?.eraseColor(0)
//设置画布的颜色
mOverlayPaint?.color?.let { mOverlayCanvas?.drawColor(it) }
mTargetPaint?.let { mTargetRectF?.let { it1 -> mOverlayCanvas?.drawRoundRect(it1,20f,20f, it) } }
mOverlayRect?.left?.toFloat()?.let {
mOverlayRect?.top?.toFloat()?.let { it1 ->
mOverlayBitmap?.let { it2 ->
canvas?.drawBitmap(
it2,
it, it1,null)
}
}
}
}
fun setTutorialsColor(color:Int){
mOverlayPaint?.color = color
}
fun setTutorialsAlpha(alpha:Int){
mOverlayPaint?.alpha = alpha
}
/**
* 设置针对控件范围以及位置
*/
fun setTargetRect(targetRect:Rect){
mTargetRectF?.set(targetRect)
}
fun init(){
//防止绘制出现问题
setWillNotDraw(false)
mOverlayRect = Rect()
val wh = TutorialsUtil.getWindowSize(context)
mOverlayRect?.set(0,0,wh[0],wh[1])
mOverlayBitmap = Bitmap.createBitmap(wh[0],wh[1],Bitmap.Config.ARGB_8888)
mOverlayCanvas = Canvas()
mOverlayCanvas?.setBitmap(mOverlayBitmap)
mOverlayPaint = Paint()
mTargetPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mTargetPaint?.setColor(Color.WHITE)
mTargetPaint?.setXfermode(PorterDuffXfermode(PorterDuff.Mode.CLEAR))
mTargetRectF = RectF()
}
}
- 修改TutorialsUtil类
package com.icez.myapplication.weight.tutorials
import android.content.Context
import android.graphics.Rect
import android.util.DisplayMetrics
import android.view.View
import android.view.WindowManager
/**
* 工具类
*/
class TutorialsUtil {
companion object{
/**
* 获取窗口的大小
*/
fun getWindowSize(context: Context): IntArray {
val wh = IntArray(2)
val wm:WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val displayMetrics:DisplayMetrics = DisplayMetrics()
wm.defaultDisplay.getRealMetrics(displayMetrics)
wh[0] = displayMetrics.widthPixels
wh[1] = displayMetrics.heightPixels
return wh
}
/**
* 计算控件的范围大小
*/
fun getViewSize(targetView: View):Rect{
val lt = IntArray(2)
targetView.getLocationInWindow(lt)
val targetRect = Rect()
targetRect.set(lt[0],lt[1],lt[0]+targetView.measuredWidth,lt[1]+targetView.measuredHeight)
return targetRect
}
}
}
调整指引控件所在位置
- 修改TutorialsView 类
package com.icez.myapplication.weight.tutorials
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.ViewGroup
/**
* 蒙层根布局
*/
class TutorialsView :ViewGroup{
var mTargetRectF:RectF ?= null
/**
* 蒙层范围
*/
var mOverlayRect: Rect ?= null
/**
* 蒙层画布
*/
var mOverlayCanvas:Canvas ?= null
/**
* 蒙层位图
*/
var mOverlayBitmap:Bitmap ?= null
var mOverlayPaint:Paint ?= null
var mTargetPaint:Paint ?= null
var mTargetViewLeft:Int = 0
var mTargetViewTop:Int = 0
var mOverylayLocation:Int = TutorialsAdapter.OVERLAY
constructor(context:Context):this(context,null)
constructor(context:Context,attrs: AttributeSet?):this(context,attrs,0)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int):super(context,attrs,defStyle){
init()
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
mTargetRectF = null
mOverlayRect = null
mOverlayCanvas = null
mOverlayBitmap = null
mOverlayPaint = null
mTargetPaint = null
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
val childCount = childCount
if(childCount>0){
val child = getChildAt(0)
when(mOverylayLocation){
TutorialsAdapter.TOP -> {
child.layout(0,0,measuredWidth,mTargetViewTop)
}
TutorialsAdapter.LEFT -> {
child.layout(0,0,mTargetViewLeft,measuredHeight)
}
TutorialsAdapter.BOTTOM -> {
val wh = TutorialsUtil.getWindowSize(context)//下面
val mTargetRectFHeight = mTargetRectF?.height()?:0
child.layout(0,mTargetViewTop + mTargetRectFHeight.toInt(),measuredWidth,mTargetViewTop + mTargetRectFHeight.toInt()+(wh[1]-(mTargetViewTop + mTargetRectFHeight.toInt())))
}
TutorialsAdapter.RIGHT -> {
val wh = TutorialsUtil.getWindowSize(context)//右边
child.layout(mTargetViewLeft+(mTargetRectF?.width()?:0).toInt(),0,mTargetViewLeft+(mTargetRectF?.width()?:0).toInt()+(wh[0]-(mTargetRectF?.width()?:0).toInt()-mTargetViewLeft),measuredHeight)
}
TutorialsAdapter.OVERLAY -> {
child.layout(0,0,measuredWidth,measuredHeight)
}
}
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
Log.e("icez","onMeasure")
//获取绘制的宽度
val w:Int = MeasureSpec.getSize(widthMeasureSpec)
//获取绘制的高度
val h:Int = MeasureSpec.getSize(heightMeasureSpec)
setMeasuredDimension(w,h)
val count:Int = childCount
var child: View?= null
//繪製子控件的大小
for(index in 0 until count){
child = getChildAt(index)
if(child != null){
//繪製子控件的大小
measureChild(child, widthMeasureSpec, heightMeasureSpec)
}
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//设置填充的位图颜色,0表示透明
mOverlayBitmap?.eraseColor(0)
//设置画布的颜色
mOverlayPaint?.color?.let { mOverlayCanvas?.drawColor(it) }
mTargetPaint?.let { mTargetRectF?.let { it1 -> mOverlayCanvas?.drawRoundRect(it1,20f,20f, it) } }
mOverlayRect?.left?.toFloat()?.let {
mOverlayRect?.top?.toFloat()?.let { it1 ->
mOverlayBitmap?.let { it2 ->
canvas?.drawBitmap(
it2,
it, it1,null)
}
}
}
}
fun setTutorialsColor(color:Int){
mOverlayPaint?.color = color
}
fun setOverylayLocation(overylayLocation:Int){
mOverylayLocation = overylayLocation
}
fun setTutorialsAlpha(alpha:Int){
mOverlayPaint?.alpha = alpha
}
/**
* 设置针对控件范围以及位置
*/
fun setTargetRect(targetRect:Rect,targetViewLeft:Int,targetViewTop:Int){
mTargetViewLeft = targetViewLeft
mTargetViewTop = targetViewTop
mTargetRectF?.set(targetRect)
}
fun init(){
//防止绘制出现问题
setWillNotDraw(false)
mOverlayRect = Rect()
val wh = TutorialsUtil.getWindowSize(context)
mOverlayRect?.set(0,0,wh[0],wh[1])
mOverlayBitmap = Bitmap.createBitmap(wh[0],wh[1],Bitmap.Config.ARGB_8888)
mOverlayCanvas = Canvas()
mOverlayCanvas?.setBitmap(mOverlayBitmap)
mOverlayPaint = Paint()
mTargetPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mTargetPaint?.setColor(Color.WHITE)
mTargetPaint?.setXfermode(PorterDuffXfermode(PorterDuff.Mode.CLEAR))
mTargetRectF = RectF()
}
}
- 修改Tutorials 类
package com.icez.myapplication.weight.tutorials
import android.app.Activity
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
/**
* 教程管理类
*/
class Tutorials : View.OnTouchListener {
var mTutorialsAdapter:TutorialsAdapter ?= null
var mTutorialsView: TutorialsView? = null
var mActivity:Activity ?= null
var mRootView:ViewGroup ?= null
var mTutorialsAttrs:TutorialsAttrs ?= null
/**
* 显示教程UI
*/
fun show(activity:Activity,rootView: ViewGroup?) {
mRootView = rootView
mActivity = activity
if (mRootView == null) {
mRootView = mActivity?.window?.decorView as ViewGroup
}
mTutorialsView = createTutorials(mActivity)
if (mRootView != null && mTutorialsView != null) {
mRootView?.addView(mTutorialsView)
}
}
/**
* 创建教程控件
*/
fun createTutorials(activity: Activity?): TutorialsView? {
val mTutorialsView: TutorialsView? = activity?.let { TutorialsView(it) }
mTutorialsView?.setTutorialsAlpha(mTutorialsAttrs?.mAlpha?:0)
mTutorialsView?.setOnTouchListener(this)
mTutorialsAttrs?.mTargetView?.let {
TutorialsUtil.getViewSize(
it
)
}?.let { mTutorialsView?.setTargetRect(it,TutorialsUtil.getViewLeftTop(mTutorialsAttrs?.mTargetView!!)[0],TutorialsUtil.getViewLeftTop(mTutorialsAttrs?.mTargetView!!)[1]) }
mTutorialsAdapter?.getPosition()?.let { mTutorialsView?.setOverylayLocation(it) }
mTutorialsView?.removeAllViews()
mTutorialsView?.addView(mTutorialsAdapter?.getView(this))
return mTutorialsView
}
/**
* 关闭教程UI
*/
fun dimiss(){
mTutorialsView?.removeAllViews()
mRootView?.removeView(mTutorialsView)
mTutorialsView = null
}
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
return true
}
}
- 修改TutorialsAdapter类
package com.icez.myapplication.weight.tutorials
import android.view.View
/**
* 子控件
*/
open interface TutorialsAdapter{
companion object{
val TOP = 0x01
val BOTTOM = 0x02
val LEFT = 0x03
val RIGHT = 0x04
val OVERLAY = 0x05
}
fun getView(mTutorials: Tutorials): View
fun getPosition():Int
}
- 修改TutorialsAttrs类
package com.icez.myapplication.weight.tutorials
import android.view.View
/**
* 教程属性
*/
data class TutorialsAttrs(
var mTargetView: View? = null,//针对控件
var mAlpha:Int = 150,
var mLocation:Int = TutorialsAdapter.OVERLAY
)
- 修改TutorialsBuilder类
package com.icez.myapplication.weight.tutorials
import android.view.View
import android.view.ViewGroup
import java.lang.RuntimeException
/**
* 教程构建器,用于自定义蒙层控件的属性
*/
class TutorialsBuilder{
var mTutorialsAttrs:TutorialsAttrs ?= null
var mTutorialsAdapter:TutorialsAdapter ?= null
init {
mTutorialsAttrs = TutorialsAttrs()
}
/**
* 设置教程控件
*/
fun setTargetView(targetView: View):TutorialsBuilder{
if(targetView==null){
throw RuntimeException("Icez hint:targetView is not null. Please check it.")
}
mTutorialsAttrs?.mTargetView = targetView
return this
}
/**
* 设置蒙层透明度
*/
fun setAlpha(alpha:Int):TutorialsBuilder{
if(alpha<0){
throw RuntimeException("Icez hint:alpha cannot be less than zero. Please check it.")
}
mTutorialsAttrs?.mAlpha = alpha
return this
}
/**
* 设置蒙层子控件
*/
fun setTutorialsAdapter(tutorialsAdapter: TutorialsAdapter):TutorialsBuilder{
if(tutorialsAdapter==null){
throw RuntimeException("Icez hint:tutorialsAdapter is not null. Please check it.")
}
mTutorialsAdapter = tutorialsAdapter
return this
}
/**
* 创建蒙层教程属性
*/
fun createTutorials():Tutorials{
val mTutorials = Tutorials()
mTutorials.mTutorialsAttrs = mTutorialsAttrs
mTutorials.mTutorialsAdapter = mTutorialsAdapter
return mTutorials
}
}
完成
到这步骤基本完成了,剩余的动画,需要自己根据项目需求去实现
希望对读者有点帮助