App遮罩层引导操作

来源:互联网 发布:java获取本地端口号 编辑:程序博客网 时间:2024/06/18 14:17

写在前面


不知道大牛们怎么实现的,自己尝试鼓捣了几个方式,有的并不可取(源代码注释有说明),只作为知识点掌握吧。

本次之所以用Android 5.0开发完成是因为有一个很好的demo(github上)是建立在此基础上的,不过你可以稍微修改就能向下兼容,我做了注释,我集成在本Demo中是倒数第二个。由于是整合,里面的资源文件自然都写在一起了,如果想分离某一种实现方式还请看懂Demo逐一分离,根据Demo分离所依赖的资源。另外,没有加入内部存储,不过包含了这个工具类,为了演示效果没有往具体功能里面添加相关判断是不是首次引导的代码,这个在跳转成功的那个连接或者按钮提示处添加就行啦,比较简单。

简单解释




单独拿出了主文件和跳转成功页面,每个包是一种实现方式,里面有包含Activity。这是src




布局中activity_是上面说的单独拿出的两个activity,layout_都是分别实现的布局文件,shade_是倒数第二种大牛实现的方式的activity。这是布局。




关于资源文件分离要慎重,这是res,慎重哟~    就是这样


实现说明


1. Splash实现方式

说白了就是利用Handler的延迟功能,核心代码如下:

[java] view plaincopy
  1. /** 
  2.  * 采用Splash延迟启动方式,实现方式就是通过handler延迟启动,splash显示的就是一个activity里面一张背景图 
  3.  * 采用这个方式将要花费一个activity消耗太大,采用visible设置显示或隐藏会更好。 
  4.  * */  
  5.     private void splashShow() {  
  6.         AnimationUtil.activityZoomAnimation(this);  
  7.         new Handler().postDelayed(new Runnable() {  
  8.               
  9.             @Override  
  10.             public void run() {  
  11.                 startActivity(new Intent(SplashActivity.this, SuessedActivity.class));  
  12.                 AnimationUtil.finishActivityAnimation(SplashActivity.this);  
  13.             }  
  14.         }, Constant.DELAY);  
  15.     }  

2.3. ViewPage实现

借助V4包下的ViewPage组件实现,这里实现两种方式,第二种是仿知乎的。核心是

[java] view plaincopy
  1. private void pageGuide() {  
  2.         ImageView imageView = null;  
  3.         guides.clear();  
  4.         //添加引导页  
  5.         for (int i = 0; i < ids.length; i++) {  
  6.             imageView = creatImageView(ids[i]);  
  7.             guides.add(imageView);  
  8.         }  
  9.           
  10.         // 当mCurDotImageView的所在的树形层次将要被绘出时此方法被调用  
  11.         mCurDotImageView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {  
  12.               
  13.             @Override  
  14.             public boolean onPreDraw() {  
  15.                 offset = mCurDotImageView.getWidth();  
  16.                 return true;  
  17.             }  
  18.         });  
  19.           
  20.         myGuidePageAdapter = new MyGuidePageAdapter(guides);  
  21.         mViewPager.setAdapter(myGuidePageAdapter);  
  22.         mViewPager.clearAnimation();  
  23.         // 添加页面滑动监听  
  24.         mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){  
  25.             @Override  
  26.             public void onPageSelected(int position) {  
  27.                 int pos = position % ids.length;  
  28.                   
  29.                 moveCursorTo(pos);  
  30.                   
  31.                 if (pos == ids.length-1) {// 到最后一张了  
  32.                     handler.sendEmptyMessageDelayed(TO_THE_END, 500);                     
  33.                 } else if (curPos == ids.length - 1) {  
  34.                     handler.sendEmptyMessageDelayed(LEAVE_FROM_END, 100);  
  35.                 }  
  36.                 curPos = pos;  
  37.                 super.onPageSelected(position);  
  38.             }  
  39.   
  40.         });  
  41.     }  

[java] view plaincopy
  1. private void moveCursorTo(int pos) {  
  2.         AnimationSet animationSet = new AnimationSet(true);  
  3.         TranslateAnimation tAnim =   
  4.                 new TranslateAnimation(offset * curPos, offset * pos, 00);  
  5.         animationSet.addAnimation(tAnim);  
  6.         animationSet.setDuration(300);  
  7.         animationSet.setFillAfter(true);  
  8.         mCurDotImageView.startAnimation(animationSet);  
  9.     }  
  10.       
  11.     private ImageView creatImageView(int id){  
  12.         ImageView iv = new ImageView(this);  
  13.         iv.setImageResource(id);  
  14.         ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(  
  15.                 ViewGroup.LayoutParams.MATCH_PARENT,  
  16.                 ViewGroup.LayoutParams.MATCH_PARENT);  
  17.         iv.setLayoutParams(params);  
  18.         iv.setScaleType(ScaleType.FIT_XY);  
  19.         return iv;  
  20.     }  

4. ScrollView实现上下的滑动,仿赶集网,重写了ScrollView,根据滑动的高度显示布局,核心:

[java] view plaincopy
  1. public void onScrollChanged(int top, int oldTop) {  
  2.         int animTop = mLinearLayout.getTop() - top;  
  3.         if (top > oldTop) {  
  4.             if (animTop < mStartAnimateTop && !hasStart) {  
  5.                 Animation anim = AnimationUtils  
  6.                         .loadAnimation(this, R.anim.show);  
  7.                 mLinearLayout.setVisibility(View.VISIBLE);  
  8.                 mLinearLayout.startAnimation(anim);  
  9.                 hasStart = true;  
  10.             }  
  11.         } else {  
  12.             if (animTop > mStartAnimateTop && hasStart) {  
  13.                 Animation anim = AnimationUtils.loadAnimation(this,  
  14.                         R.anim.close);  
  15.                 mLinearLayout.setVisibility(View.INVISIBLE);  
  16.                 mLinearLayout.startAnimation(anim);  
  17.                 hasStart = false;  
  18.             }  
  19.         }  
  20.     }  

5. 用美工的图片进行遮罩,好笨的方式,很简单实现,主要是布局,控制显示和不显示。

[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"  
  5.     android:orientation="vertical" >  
  6.       
  7.     <TextView   
  8.         android:id="@+id/tv"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:layout_centerInParent="true"  
  12.         android:text="我是新功能"/>  
  13.    <RelativeLayout   
  14.        android:id="@+id/tip_rl"  
  15.        android:layout_width="match_parent"  
  16.        android:layout_height="match_parent"  
  17.        android:background="@drawable/g_tips">  
  18.   
  19.    </RelativeLayout>  
  20. </RelativeLayout>  
6. WindowManager实现【不推荐使用】

因为它是系统级别的Window,是整个OS唯一的Window,所以出现引导按home键会有问题,可以利用它实现悬浮框,比如360的小球。核心如下,通过addView(),removeView();实现

[java] view plaincopy
  1. private void initParam() {  
  2.         params = new LayoutParams();  
  3.         // 类型  
  4.         params.type = LayoutParams.TYPE_SYSTEM_ALERT;  
  5.         // flags  
  6.         params.flags = LayoutParams.FLAG_ALT_FOCUSABLE_IM;  
  7.         // 如果设置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,弹出的View收不到Back键的事件  
  8.         // 格式(半透明)  
  9.         params.format = PixelFormat.TRANSLUCENT;  
  10.         params.width = LayoutParams.MATCH_PARENT;  
  11.         params.height = LayoutParams.MATCH_PARENT;  
  12.     }  
  13.       
  14.     /** 
  15.      * @param view 遮罩层view 
  16.      * @param x 
  17.      * @param y  位置 
  18.      * */  
  19.     public void display(int layoutId) {  
  20.         initParam();  
  21.         final View view = LayoutInflater.from(context).inflate(layoutId, null);  
  22.           
  23.         mWindowManager.addView(view, params);  
  24.         view.setOnClickListener(new OnClickListener() {  
  25.               
  26.             @Override  
  27.             public void onClick(View v) {  
  28.                 if (view.getParent() != null) {  
  29.                     mWindowManager.removeView(view);  
  30.                 }  
  31.                   
  32.             }  
  33.         });  

7. getWindow().getDecorView()实现,这个呢和上一种方式区别就是获取的是当前布局的根View,利用FramLayout的特性实现的,通过用户传入几张遮罩图就分多少部遮罩显示实现连贯性,当然传入一个就遮罩步骤一个呗:

[java] view plaincopy
  1. public void display() {  
  2.         ViewParent viewParent =  rootView.getParent();  
  3.           
  4.         if (viewParent instanceof FrameLayout) {  
  5.             if (length > 0 && current < length) {  
  6.                 final FrameLayout container = (FrameLayout) viewParent;  
  7.                 final ImageView imageView = new ImageView(activity);  
  8.                 imageView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));  
  9.                 imageView.setBackgroundResource(coverIds[current]);  
  10.                 imageView.setScaleType(ScaleType.FIT_XY);  
  11.                 imageView.setOnClickListener(new OnClickListener() {  
  12.                       
  13.                     @Override  
  14.                     public void onClick(View v) {  
  15.                         container.removeView(imageView);  
  16.                         handler.sendEmptyMessage(CONTINUE);  
  17.                     }  
  18.                 });  
  19.                 current ++ ;  
  20.                 container.addView(imageView);  
  21.             }  
  22.         }  
  23.     }  

8. popubwindow实现,这个是采用它里面的SetContentView(View)实现的:

[java] view plaincopy
  1. @SuppressWarnings("deprecation")  
  2.     public void init(int layoutId) {  
  3.         View view = LayoutInflater.from(context).inflate(layoutId, null);  
  4.         this.setOutsideTouchable(true);  
  5.         this.setBackgroundDrawable(new BitmapDrawable());  
  6.         this.setHeight(LayoutParams.MATCH_PARENT);  
  7.         this.setWidth(LayoutParams.MATCH_PARENT);  
  8.         this.setFocusable(true);  
  9.         this.setContentView(view);  
  10.         this.setAnimationStyle(R.style.dialog_animation);  
  11.         view.setOnClickListener(new OnClickListener() {  
  12.               
  13.             @Override  
  14.             public void onClick(View v) {  
  15.                 popuWindowHelper.dismiss();  
  16.             }  
  17.         });  
  18.           
  19.     }  

9. 自定义View实现【大牛】,值得推荐

其核心就是自定义一个View并添加点击事件监听,为了做到连贯性,采用一个suquence队列显示需要引导的item,用这个队列管理引导步骤,如果是单纯的一个指示的话就用不到该队列了。。

[java] view plaincopy
  1. package com.lzy.shadeguide.child.shade.way_three.thrid_lib;  
  2.   
  3. import android.annotation.SuppressLint;  
  4. import android.annotation.TargetApi;  
  5. import android.app.Activity;  
  6. import android.content.Context;  
  7. import android.graphics.Bitmap;  
  8. import android.graphics.Canvas;  
  9. import android.graphics.Color;  
  10. import android.graphics.Paint;  
  11. import android.graphics.Point;  
  12. import android.graphics.PorterDuff;  
  13. import android.graphics.PorterDuffXfermode;  
  14. import android.graphics.Rect;  
  15. import android.os.Build;  
  16. import android.os.Handler;  
  17. import android.text.TextUtils;  
  18. import android.util.AttributeSet;  
  19. import android.util.DisplayMetrics;  
  20. import android.view.Gravity;  
  21. import android.view.LayoutInflater;  
  22. import android.view.MotionEvent;  
  23. import android.view.View;  
  24. import android.view.ViewGroup;  
  25. import android.view.ViewTreeObserver;  
  26. import android.widget.FrameLayout;  
  27. import android.widget.TextView;  
  28.   
  29. import java.util.ArrayList;  
  30. import java.util.List;  
  31.   
  32. import com.lzy.shadeguide.R;  
  33. import com.lzy.shadeguide.child.shade.way_three.thrid_lib.shape.CircleShape;  
  34. import com.lzy.shadeguide.child.shade.way_three.thrid_lib.shape.NoShape;  
  35. import com.lzy.shadeguide.child.shade.way_three.thrid_lib.shape.RectangleShape;  
  36. import com.lzy.shadeguide.child.shade.way_three.thrid_lib.shape.Shape;  
  37. import com.lzy.shadeguide.child.shade.way_three.thrid_lib.target.Target;  
  38. import com.lzy.shadeguide.child.shade.way_three.thrid_lib.target.ViewTarget;  
  39.   
  40.   
  41. /** 
  42.  * Helper class to show a sequence of showcase views. 
  43.  */  
  44. public class MaterialShowcaseView extends FrameLayout implements View.OnTouchListener, View.OnClickListener {  
  45.   
  46.     private int mOldHeight;  
  47.     private int mOldWidth;  
  48.     private Bitmap mBitmap;// = new WeakReference<>(null);  
  49.     private Canvas mCanvas;  
  50.     private Paint mEraser;  
  51.     private Target mTarget;  
  52.     private Shape mShape;  
  53.     private int mXPosition;  
  54.     private int mYPosition;  
  55.     private boolean mWasDismissed = false;  
  56.     private int mShapePadding = ShowcaseConfig.DEFAULT_SHAPE_PADDING;  
  57.   
  58.     private View mContentBox;  
  59.     private TextView mContentTextView;  
  60.     private TextView mDismissButton;  
  61.     private int mGravity;  
  62.     private int mContentBottomMargin;  
  63.     private int mContentTopMargin;  
  64.     private boolean mDismissOnTouch = false;  
  65.     private boolean mShouldRender = false// flag to decide when we should actually render  
  66.     private int mMaskColour;  
  67.     private AnimationFactory mAnimationFactory;  
  68.     private boolean mShouldAnimate = true;  
  69.     private long mFadeDurationInMillis = ShowcaseConfig.DEFAULT_FADE_TIME;  
  70.     private Handler mHandler;  
  71.     private long mDelayInMillis = ShowcaseConfig.DEFAULT_DELAY;  
  72.     private int mBottomMargin = 0;  
  73.     private boolean mSingleUse = false// should display only once  
  74.     private PrefsManager mPrefsManager; // used to store state doe single use mode  
  75.     List<IShowcaseListener> mListeners; // external listeners who want to observe when we show and dismiss  
  76.     private UpdateOnGlobalLayout mLayoutListener;  
  77.     private IDetachedListener mDetachedListener;  
  78.   
  79.     public MaterialShowcaseView(Context context) {  
  80.         super(context);  
  81.         init(context);  
  82.     }  
  83.   
  84.     public MaterialShowcaseView(Context context, AttributeSet attrs) {  
  85.         super(context, attrs);  
  86.         init(context);  
  87.     }  
  88.   
  89.     public MaterialShowcaseView(Context context, AttributeSet attrs, int defStyleAttr) {  
  90.         super(context, attrs, defStyleAttr);  
  91.         init(context);  
  92.     }  
  93.   
  94.     @TargetApi(Build.VERSION_CODES.LOLLIPOP)  //Android 5.0  
  95.     public MaterialShowcaseView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {  
  96.         super(context, attrs, defStyleAttr, defStyleRes);  
  97.         init(context);  
  98.     }  
  99.   
  100.   
  101.     private void init(Context context) {  
  102.         setWillNotDraw(false);  
  103.   
  104.         // create our animation factory  
  105.         mAnimationFactory = new AnimationFactory();  
  106.   
  107.         mListeners = new ArrayList<IShowcaseListener>();  
  108.   
  109.         // make sure we add a global layout listener so we can adapt to changes  
  110.         mLayoutListener = new UpdateOnGlobalLayout();  
  111.         getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener);  
  112.   
  113.         // consume touch events  
  114.         setOnTouchListener(this);  
  115.   
  116.         mMaskColour = Color.parseColor(ShowcaseConfig.DEFAULT_MASK_COLOUR);  
  117.         setVisibility(INVISIBLE);  
  118.   
  119.   
  120.         View contentView = LayoutInflater.from(getContext()).inflate(R.layout.shade_showcase_content, thistrue);  
  121.         mContentBox = contentView.findViewById(R.id.content_box);  
  122.         mContentTextView = (TextView) contentView.findViewById(R.id.tv_content);  
  123.         mDismissButton = (TextView) contentView.findViewById(R.id.tv_dismiss);  
  124.         mDismissButton.setOnClickListener(this);  
  125.     }  
  126.   
  127.   
  128.     /** 
  129.      * Interesting drawing stuff. 
  130.      * We draw a block of semi transparent colour to fill the whole screen then we draw of transparency 
  131.      * to create a circular "viewport" through to the underlying content 
  132.      * 
  133.      * @param canvas 
  134.      */  
  135.     @Override  
  136.     protected void onDraw(Canvas canvas) {  
  137.         super.onDraw(canvas);  
  138.   
  139.         // don't bother drawing if we're not ready  
  140.         if (!mShouldRender) return;  
  141.   
  142.         // get current dimensions  
  143.         int width = getMeasuredWidth();  
  144.         int height = getMeasuredHeight();  
  145.   
  146.         // build a new canvas if needed i.e first pass or new dimensions  
  147.         if (mBitmap == null || mCanvas == null || mOldHeight != height || mOldWidth != width) {  
  148.   
  149.             if (mBitmap != null) mBitmap.recycle();  
  150.   
  151.             mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);  
  152.   
  153.   
  154.             mCanvas = new Canvas(mBitmap);  
  155.         }  
  156.   
  157.         // save our 'old' dimensions  
  158.         mOldWidth = width;  
  159.         mOldHeight = height;  
  160.   
  161.         // clear canvas  
  162.         mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);  
  163.   
  164.         // draw solid background  
  165.         mCanvas.drawColor(mMaskColour);  
  166.   
  167.         // Prepare eraser Paint if needed  
  168.         if (mEraser == null) {  
  169.             mEraser = new Paint();  
  170.             mEraser.setColor(0xFFFFFFFF);  
  171.             mEraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));  
  172.             mEraser.setFlags(Paint.ANTI_ALIAS_FLAG);  
  173.         }  
  174.   
  175.         // draw (erase) shape  
  176.         mShape.draw(mCanvas, mEraser, mXPosition, mYPosition, mShapePadding);  
  177.   
  178.         // Draw the bitmap on our views  canvas.  
  179.         canvas.drawBitmap(mBitmap, 00null);  
  180.     }  
  181.   
  182.     @Override  
  183.     protected void onDetachedFromWindow() {  
  184.         super.onDetachedFromWindow();  
  185.   
  186.         /** 
  187.          * If we're being detached from the window without the mWasDismissed flag then we weren't purposefully dismissed 
  188.          * Probably due to an orientation change or user backed out of activity. 
  189.          * Ensure we reset the flag so the showcase display again. 
  190.          */  
  191.         if (!mWasDismissed && mSingleUse && mPrefsManager != null) {  
  192.             mPrefsManager.resetShowcase();  
  193.         }  
  194.   
  195.   
  196.         notifyOnDismissed();  
  197.   
  198.     }  
  199.   
  200.     @Override  
  201.     public boolean onTouch(View v, MotionEvent event) {  
  202.         if (mDismissOnTouch) {  
  203.             hide();  
  204.         }  
  205.         return true;  
  206.     }  
  207.   
  208.   
  209.     private void notifyOnDisplayed() {  
  210.         for (IShowcaseListener listener : mListeners) {  
  211.             listener.onShowcaseDisplayed(this);  
  212.         }  
  213.     }  
  214.   
  215.     private void notifyOnDismissed() {  
  216.         if (mListeners != null) {  
  217.             for (IShowcaseListener listener : mListeners) {  
  218.                 listener.onShowcaseDismissed(this);  
  219.             }  
  220.   
  221.             mListeners.clear();  
  222.             mListeners = null;  
  223.         }  
  224.   
  225.         /** 
  226.          * internal listener used by sequence for storing progress within the sequence 
  227.          */  
  228.         if (mDetachedListener != null) {  
  229.             mDetachedListener.onShowcaseDetached(this, mWasDismissed);  
  230.         }  
  231.     }  
  232.   
  233.     /** 
  234.      * Dismiss button clicked 
  235.      * 
  236.      * @param v 
  237.      */  
  238.     @Override  
  239.     public void onClick(View v) {  
  240.         hide();  
  241.     }  
  242.   
  243.     /** 
  244.      * Tells us about the "Target" which is the view we want to anchor to. 
  245.      * We figure out where it is on screen and (optionally) how big it is. 
  246.      * We also figure out whether to place our content and dismiss button above or below it. 
  247.      * 
  248.      * @param target 
  249.      */  
  250.     public void setTarget(Target target) {  
  251.         mTarget = target;  
  252.   
  253.         // update dismiss button state  
  254.         updateDismissButton();  
  255.   
  256.         if (mTarget != null) {  
  257.   
  258.             /** 
  259.              * If we're on lollipop then make sure we don't draw over the nav bar 
  260.              */  
  261.             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  
  262.                 mBottomMargin = getSoftButtonsBarSizePort((Activity) getContext());  
  263.                 FrameLayout.LayoutParams contentLP = (LayoutParams) getLayoutParams();  
  264.   
  265.                 if (contentLP != null && contentLP.bottomMargin != mBottomMargin)  
  266.                     contentLP.bottomMargin = mBottomMargin;  
  267.             }  
  268.   
  269.             // apply the target position  
  270.             Point targetPoint = mTarget.getPoint();  
  271.             Rect targetBounds = mTarget.getBounds();  
  272.             setPosition(targetPoint);  
  273.   
  274.             // now figure out whether to put content above or below it  
  275.             int height = getMeasuredHeight();  
  276.             int midPoint = height / 2;  
  277.             int yPos = targetPoint.y;  
  278.   
  279.             int radius = Math.max(targetBounds.height(), targetBounds.width()) / 2;  
  280.             if (mShape != null) {  
  281.                 mShape.updateTarget(mTarget);  
  282.                 radius = mShape.getHeight() / 2;  
  283.             }  
  284.   
  285.             if (yPos > midPoint) {  
  286.                 // target is in lower half of screen, we'll sit above it  
  287.                 mContentTopMargin = 0;  
  288.                 mContentBottomMargin = (height - yPos) + radius + mShapePadding;  
  289.                 mGravity = Gravity.BOTTOM;  
  290.             } else {  
  291.                 // target is in upper half of screen, we'll sit below it  
  292.                 mContentTopMargin = yPos + radius + mShapePadding;  
  293.                 mContentBottomMargin = 0;  
  294.                 mGravity = Gravity.TOP;  
  295.             }  
  296.         }  
  297.   
  298.         applyLayoutParams();  
  299.     }  
  300.   
  301.     private void applyLayoutParams() {  
  302.   
  303.         if (mContentBox != null && mContentBox.getLayoutParams() != null) {  
  304.             FrameLayout.LayoutParams contentLP = (LayoutParams) mContentBox.getLayoutParams();  
  305.   
  306.             boolean layoutParamsChanged = false;  
  307.   
  308.             if (contentLP.bottomMargin != mContentBottomMargin) {  
  309.                 contentLP.bottomMargin = mContentBottomMargin;  
  310.                 layoutParamsChanged = true;  
  311.             }  
  312.   
  313.             if (contentLP.topMargin != mContentTopMargin) {  
  314.                 contentLP.topMargin = mContentTopMargin;  
  315.                 layoutParamsChanged = true;  
  316.             }  
  317.   
  318.             if (contentLP.gravity != mGravity) {  
  319.                 contentLP.gravity = mGravity;  
  320.                 layoutParamsChanged = true;  
  321.             }  
  322.   
  323.             /** 
  324.              * Only apply the layout params if we've actually changed them, otherwise we'll get stuck in a layout loop 
  325.              */  
  326.             if (layoutParamsChanged)  
  327.                 mContentBox.setLayoutParams(contentLP);  
  328.         }  
  329.     }  
  330.   
  331.     /** 
  332.      * SETTERS 
  333.      */  
  334.   
  335.     void setPosition(Point point) {  
  336.         setPosition(point.x, point.y);  
  337.     }  
  338.   
  339.     void setPosition(int x, int y) {  
  340.         mXPosition = x;  
  341.         mYPosition = y;  
  342.     }  
  343.   
  344.     private void setContentText(CharSequence contentText) {  
  345.         if (mContentTextView != null) {  
  346.             mContentTextView.setText(contentText);  
  347.         }  
  348.     }  
  349.   
  350.     private void setDismissText(CharSequence dismissText) {  
  351.         if (mDismissButton != null) {  
  352.             mDismissButton.setText(dismissText);  
  353.   
  354.             updateDismissButton();  
  355.         }  
  356.     }  
  357.   
  358.     private void setContentTextColor(int textColour) {  
  359.         if (mContentTextView != null) {  
  360.             mContentTextView.setTextColor(textColour);  
  361.         }  
  362.     }  
  363.   
  364.     private void setDismissTextColor(int textColour) {  
  365.         if (mDismissButton != null) {  
  366.             mDismissButton.setTextColor(textColour);  
  367.         }  
  368.     }  
  369.   
  370.     private void setShapePadding(int padding) {  
  371.         mShapePadding = padding;  
  372.     }  
  373.   
  374.     private void setDismissOnTouch(boolean dismissOnTouch) {  
  375.         mDismissOnTouch = dismissOnTouch;  
  376.     }  
  377.   
  378.     private void setShouldRender(boolean shouldRender) {  
  379.         mShouldRender = shouldRender;  
  380.     }  
  381.   
  382.     private void setMaskColour(int maskColour) {  
  383.         mMaskColour = maskColour;  
  384.     }  
  385.   
  386.     private void setDelay(long delayInMillis) {  
  387.         mDelayInMillis = delayInMillis;  
  388.     }  
  389.   
  390.     private void setFadeDuration(long fadeDurationInMillis) {  
  391.         mFadeDurationInMillis = fadeDurationInMillis;  
  392.     }  
  393.   
  394.     public void addShowcaseListener(IShowcaseListener showcaseListener) {  
  395.         mListeners.add(showcaseListener);  
  396.     }  
  397.   
  398.     public void removeShowcaseListener(MaterialShowcaseSequence showcaseListener) {  
  399.         if (mListeners.contains(showcaseListener)) {  
  400.             mListeners.remove(showcaseListener);  
  401.         }  
  402.     }  
  403.   
  404.     void setDetachedListener(IDetachedListener detachedListener) {  
  405.         mDetachedListener = detachedListener;  
  406.     }  
  407.   
  408.     public void setShape(Shape mShape) {  
  409.         this.mShape = mShape;  
  410.     }  
  411.   
  412.     /** 
  413.      * Set properties based on a config object 
  414.      * 
  415.      * @param config 
  416.      */  
  417.     public void setConfig(ShowcaseConfig config) {  
  418.         setDelay(config.getDelay());  
  419.         setFadeDuration(config.getFadeDuration());  
  420.         setContentTextColor(config.getContentTextColor());  
  421.         setDismissTextColor(config.getDismissTextColor());  
  422.         setMaskColour(config.getMaskColor());  
  423.         setShape(config.getShape());  
  424.         setShapePadding(config.getShapePadding());  
  425.     }  
  426.   
  427.     private void updateDismissButton() {  
  428.         // hide or show button  
  429.         if (mDismissButton != null) {  
  430.             if (TextUtils.isEmpty(mDismissButton.getText())) {  
  431.                 mDismissButton.setVisibility(GONE);  
  432.             } else {  
  433.                 mDismissButton.setVisibility(VISIBLE);  
  434.             }  
  435.         }  
  436.     }  
  437.   
  438.     public boolean hasFired() {  
  439.         return mPrefsManager.hasFired();  
  440.     }  
  441.   
  442.     /** 
  443.      * REDRAW LISTENER - this ensures we redraw after activity finishes laying out 
  444.      */  
  445.     private class UpdateOnGlobalLayout implements ViewTreeObserver.OnGlobalLayoutListener {  
  446.   
  447.         @Override  
  448.         public void onGlobalLayout() {  
  449.             setTarget(mTarget);  
  450.         }  
  451.     }  
  452.   
  453.   
  454.     /** 
  455.      * BUILDER CLASS 
  456.      * Gives us a builder utility class with a fluent API for eaily configuring showcase views 
  457.      */  
  458.     public static class Builder {  
  459.         private static final int CIRCLE_SHAPE = 0;  
  460.         private static final int RECTANGLE_SHAPE = 1;  
  461.         private static final int NO_SHAPE = 2;  
  462.   
  463.         private boolean fullWidth = false;  
  464.         private int shapeType = CIRCLE_SHAPE;  
  465.   
  466.         final MaterialShowcaseView showcaseView;  
  467.   
  468.         private final Activity activity;  
  469.   
  470.         public Builder(Activity activity) {  
  471.             this.activity = activity;  
  472.   
  473.             showcaseView = new MaterialShowcaseView(activity);  
  474.         }  
  475.   
  476.         /** 
  477.          * Set the title text shown on the ShowcaseView. 
  478.          */  
  479.         public Builder setTarget(View target) {  
  480.             showcaseView.setTarget(new ViewTarget(target));  
  481.             return this;  
  482.         }  
  483.   
  484.         /** 
  485.          * Set the title text shown on the ShowcaseView. 
  486.          */  
  487.         public Builder setDismissText(int resId) {  
  488.             return setDismissText(activity.getString(resId));  
  489.         }  
  490.   
  491.         public Builder setDismissText(CharSequence dismissText) {  
  492.             showcaseView.setDismissText(dismissText);  
  493.             return this;  
  494.         }  
  495.   
  496.         /** 
  497.          * Set the title text shown on the ShowcaseView. 
  498.          */  
  499.         public Builder setContentText(int resId) {  
  500.             return setContentText(activity.getString(resId));  
  501.         }  
  502.   
  503.         /** 
  504.          * Set the descriptive text shown on the ShowcaseView. 
  505.          */  
  506.         public Builder setContentText(CharSequence text) {  
  507.             showcaseView.setContentText(text);  
  508.             return this;  
  509.         }  
  510.   
  511.   
  512.         public Builder setDismissOnTouch(boolean dismissOnTouch) {  
  513.             showcaseView.setDismissOnTouch(dismissOnTouch);  
  514.             return this;  
  515.         }  
  516.   
  517.         public Builder setMaskColour(int maskColour) {  
  518.             showcaseView.setMaskColour(maskColour);  
  519.             return this;  
  520.         }  
  521.   
  522.         public Builder setContentTextColor(int textColour) {  
  523.             showcaseView.setContentTextColor(textColour);  
  524.             return this;  
  525.         }  
  526.   
  527.         public Builder setDismissTextColor(int textColour) {  
  528.             showcaseView.setDismissTextColor(textColour);  
  529.             return this;  
  530.         }  
  531.   
  532.         public Builder setDelay(int delayInMillis) {  
  533.             showcaseView.setDelay(delayInMillis);  
  534.             return this;  
  535.         }  
  536.   
  537.         public Builder setFadeDuration(int fadeDurationInMillis) {  
  538.             showcaseView.setFadeDuration(fadeDurationInMillis);  
  539.             return this;  
  540.         }  
  541.   
  542.         public Builder setListener(IShowcaseListener listener) {  
  543.             showcaseView.addShowcaseListener(listener);  
  544.             return this;  
  545.         }  
  546.   
  547.         public Builder singleUse(String showcaseID) {  
  548.             showcaseView.singleUse(showcaseID);  
  549.             return this;  
  550.         }  
  551.   
  552.         public Builder setShape(Shape shape) {  
  553.             showcaseView.setShape(shape);  
  554.             return this;  
  555.         }  
  556.   
  557.         public Builder withCircleShape() {  
  558.             shapeType = CIRCLE_SHAPE;  
  559.             return this;  
  560.         }  
  561.   
  562.         public Builder withoutShape() {  
  563.             shapeType = NO_SHAPE;  
  564.             return this;  
  565.         }  
  566.   
  567.         public Builder setShapePadding(int padding) {  
  568.             showcaseView.setShapePadding(padding);  
  569.             return this;  
  570.         }  
  571.   
  572.         public Builder withRectangleShape() {  
  573.             return withRectangleShape(false);  
  574.         }  
  575.   
  576.         public Builder withRectangleShape(boolean fullWidth) {  
  577.             this.shapeType = RECTANGLE_SHAPE;  
  578.             this.fullWidth = fullWidth;  
  579.             return this;  
  580.         }  
  581.   
  582.         public MaterialShowcaseView build() {  
  583.             if (showcaseView.mShape == null) {  
  584.                 switch (shapeType) {  
  585.                     case RECTANGLE_SHAPE: {  
  586.                         showcaseView.setShape(new RectangleShape(showcaseView.mTarget.getBounds(), fullWidth));  
  587.                         break;  
  588.                     }  
  589.                     case CIRCLE_SHAPE: {  
  590.                         showcaseView.setShape(new CircleShape(showcaseView.mTarget));  
  591.                         break;  
  592.                     }  
  593.                     case NO_SHAPE: {  
  594.                         showcaseView.setShape(new NoShape());  
  595.                         break;  
  596.                     }  
  597.                     default:  
  598.                         throw new IllegalArgumentException("Unsupported shape type: " + shapeType);  
  599.                 }  
  600.             }  
  601.   
  602.             return showcaseView;  
  603.         }  
  604.   
  605.         public MaterialShowcaseView show() {  
  606.             build().show(activity);  
  607.             return showcaseView;  
  608.         }  
  609.   
  610.     }  
  611.   
  612.     private void singleUse(String showcaseID) {  
  613.         mSingleUse = true;  
  614.         mPrefsManager = new PrefsManager(getContext(), showcaseID);  
  615.     }  
  616.   
  617.     @SuppressWarnings("deprecation")  
  618.     public void removeFromWindow() {  
  619.         if (getParent() != null && getParent() instanceof ViewGroup) {  
  620.             ((ViewGroup) getParent()).removeView(this);  
  621.         }  
  622.   
  623.         if (mBitmap != null) {  
  624.             mBitmap.recycle();  
  625.             mBitmap = null;  
  626.         }  
  627.   
  628.         mEraser = null;  
  629.         mAnimationFactory = null;  
  630.         mCanvas = null;  
  631.         mHandler = null;  
  632.   
  633.         getViewTreeObserver().removeGlobalOnLayoutListener(mLayoutListener);  
  634.         mLayoutListener = null;  
  635.   
  636.         if (mPrefsManager != null)  
  637.             mPrefsManager.close();  
  638.   
  639.         mPrefsManager = null;  
  640.   
  641.   
  642.     }  
  643.   
  644.   
  645.     /** 
  646.      * Reveal the showcaseview. Returns a boolean telling us whether we actually did show anything 
  647.      * 
  648.      * @param activity 
  649.      * @return 
  650.      */  
  651.     public boolean show(final Activity activity) {  
  652.   
  653.         /** 
  654.          * if we're in single use mode and have already shot our bolt then do nothing 
  655.          */  
  656.         if (mSingleUse) {  
  657.             if (mPrefsManager.hasFired()) {  
  658.                 return false;  
  659.             } else {  
  660.                 mPrefsManager.setFired();  
  661.             }  
  662.         }  
  663.   
  664.         ((ViewGroup) activity.getWindow().getDecorView()).addView(this);  
  665.   
  666.         setShouldRender(true);  
  667.   
  668.         mHandler = new Handler();  
  669.         mHandler.postDelayed(new Runnable() {  
  670.             @Override  
  671.             public void run() {  
  672.   
  673.                 if (mShouldAnimate) {  
  674.                     fadeIn();  
  675.                 } else {  
  676.                     setVisibility(VISIBLE);  
  677.                     notifyOnDisplayed();  
  678.                 }  
  679.             }  
  680.         }, mDelayInMillis);  
  681.   
  682.         updateDismissButton();  
  683.   
  684.         return true;  
  685.     }  
  686.   
  687.   
  688.     public void hide() {  
  689.   
  690.         /** 
  691.          * This flag is used to indicate to onDetachedFromWindow that the showcase view was dismissed purposefully (by the user or programmatically) 
  692.          */  
  693.         mWasDismissed = true;  
  694.   
  695.         if (mShouldAnimate) {  
  696.             fadeOut();  
  697.         } else {  
  698.             removeFromWindow();  
  699.         }  
  700.     }  
  701.   
  702.     public void fadeIn() {  
  703.         setVisibility(INVISIBLE);  
  704.   
  705.         mAnimationFactory.fadeInView(this, mFadeDurationInMillis,  
  706.                 new IAnimationFactory.AnimationStartListener() {  
  707.                     @Override  
  708.                     public void onAnimationStart() {  
  709.                         setVisibility(View.VISIBLE);  
  710.                         notifyOnDisplayed();  
  711.                     }  
  712.                 }  
  713.         );  
  714.     }  
  715.   
  716.     public void fadeOut() {  
  717.   
  718.         mAnimationFactory.fadeOutView(this, mFadeDurationInMillis, new IAnimationFactory.AnimationEndListener() {  
  719.             @Override  
  720.             public void onAnimationEnd() {  
  721.                 setVisibility(INVISIBLE);  
  722.                 removeFromWindow();  
  723.             }  
  724.         });  
  725.     }  
  726.   
  727.     public void resetSingleUse() {  
  728.         if (mSingleUse && mPrefsManager != null) mPrefsManager.resetShowcase();  
  729.     }  
  730.   
  731.     /** 
  732.      * Static helper method for resetting single use flag 
  733.      * 
  734.      * @param context 
  735.      * @param showcaseID 
  736.      */  
  737.     public static void resetSingleUse(Context context, String showcaseID) {  
  738.         PrefsManager.resetShowcase(context, showcaseID);  
  739.     }  
  740.   
  741.     /** 
  742.      * Static helper method for resetting all single use flags 
  743.      * 
  744.      * @param context 
  745.      */  
  746.     public static void resetAll(Context context) {  
  747.         PrefsManager.resetAll(context);  
  748.     }  
  749.   
  750.     @SuppressLint("NewApi")  
  751.     public static int getSoftButtonsBarSizePort(Activity activity) {  
  752.         // getRealMetrics is only available with API 17 and +  
  753.         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {  
  754.             DisplayMetrics metrics = new DisplayMetrics();  
  755.             activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);  
  756.             int usableHeight = metrics.heightPixels;  
  757.             activity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);  
  758.             int realHeight = metrics.heightPixels;  
  759.             if (realHeight > usableHeight)  
  760.                 return realHeight - usableHeight;  
  761.             else  
  762.                 return 0;  
  763.         }  
  764.         return 0;  
  765.     }  
  766.   
  767. }  

队列管理:

[java] view plaincopy
  1. package com.lzy.shadeguide.child.shade.way_three.thrid_lib;  
  2.   
  3. import android.app.Activity;  
  4. import android.view.View;  
  5.   
  6. import java.util.LinkedList;  
  7. import java.util.Queue;  
  8.   
  9.   
  10. public class MaterialShowcaseSequence implements IDetachedListener {  
  11.   
  12.     PrefsManager mPrefsManager;  
  13.     Queue<MaterialShowcaseView> mShowcaseQueue;  
  14.     private boolean mSingleUse = false;  
  15.     Activity mActivity;  
  16.     private ShowcaseConfig mConfig;  
  17.     private int mSequencePosition = 0;  
  18.   
  19.     private OnSequenceItemShownListener mOnItemShownListener = null;  
  20.     private OnSequenceItemDismissedListener mOnItemDismissedListener = null;  
  21.   
  22.     public MaterialShowcaseSequence(Activity activity) {  
  23.         mActivity = activity;  
  24.         mShowcaseQueue = new LinkedList<MaterialShowcaseView>();  
  25.     }  
  26.   
  27.     public MaterialShowcaseSequence(Activity activity, String sequenceID) {  
  28.         this(activity);  
  29.         this.singleUse(sequenceID);  
  30.     }  
  31.   
  32.     public MaterialShowcaseSequence addSequenceItem(View targetView, String content, String dismissText) {  
  33.   
  34.         MaterialShowcaseView sequenceItem = new MaterialShowcaseView.Builder(mActivity)  
  35.                 .setTarget(targetView)  
  36.                 .setDismissText(dismissText)  
  37.                 .setContentText(content)  
  38.                 .build();  
  39.   
  40.         if (mConfig != null) {  
  41.             sequenceItem.setConfig(mConfig);  
  42.         }  
  43.   
  44.         mShowcaseQueue.add(sequenceItem);  
  45.         return this;  
  46.     }  
  47.   
  48.     public MaterialShowcaseSequence addSequenceItem(MaterialShowcaseView sequenceItem) {  
  49.         mShowcaseQueue.add(sequenceItem);  
  50.         return this;  
  51.     }  
  52.   
  53.     public MaterialShowcaseSequence singleUse(String sequenceID) {  
  54.         mSingleUse = true;  
  55.         mPrefsManager = new PrefsManager(mActivity, sequenceID);  
  56.         return this;  
  57.     }  
  58.   
  59.     public void setOnItemShownListener(OnSequenceItemShownListener listener) {  
  60.         this.mOnItemShownListener = listener;  
  61.     }  
  62.   
  63.     public void setOnItemDismissedListener(OnSequenceItemDismissedListener listener) {  
  64.         this.mOnItemDismissedListener = listener;  
  65.     }  
  66.   
  67.     public boolean hasFired() {  
  68.   
  69.         if (mPrefsManager.getSequenceStatus() == PrefsManager.SEQUENCE_FINISHED) {  
  70.             return true;  
  71.         }  
  72.   
  73.         return false;  
  74.     }  
  75.   
  76.     public void start() {  
  77.   
  78.         /** 
  79.          * Check if we've already shot our bolt and bail out if so         * 
  80.          */  
  81.         if (mSingleUse) {  
  82.             if (hasFired()) {  
  83.                 return;  
  84.             }  
  85.   
  86.             /** 
  87.              * See if we have started this sequence before, if so then skip to the point we reached before 
  88.              * instead of showing the user everything from the start 
  89.              */  
  90.             mSequencePosition = mPrefsManager.getSequenceStatus();  
  91.   
  92.             if (mSequencePosition > 0) {  
  93.                 for (int i = 0; i < mSequencePosition; i++) {  
  94.                     mShowcaseQueue.poll();  
  95.                 }  
  96.             }  
  97.         }  
  98.   
  99.   
  100.         // do start  
  101.         if (mShowcaseQueue.size() > 0)  
  102.             showNextItem();  
  103.     }  
  104.   
  105.     private void showNextItem() {  
  106.   
  107.         if (mShowcaseQueue.size() > 0 && !mActivity.isFinishing()) {  
  108.             MaterialShowcaseView sequenceItem = mShowcaseQueue.remove();  
  109.             sequenceItem.setDetachedListener(this);  
  110.             sequenceItem.show(mActivity);  
  111.             if (mOnItemShownListener != null) {  
  112.                 mOnItemShownListener.onShow(sequenceItem, mSequencePosition);  
  113.             }  
  114.         } else {  
  115.             /** 
  116.              * We've reached the end of the sequence, save the fired state 
  117.              */  
  118.             if (mSingleUse) {  
  119.                 mPrefsManager.setFired();  
  120.             }  
  121.         }  
  122.     }  
  123.   
  124.   
  125.     @Override  
  126.     public void onShowcaseDetached(MaterialShowcaseView showcaseView, boolean wasDismissed) {  
  127.   
  128.         showcaseView.setDetachedListener(null);  
  129.   
  130.         /** 
  131.          * We're only interested if the showcase was purposefully dismissed 
  132.          */  
  133.         if (wasDismissed) {  
  134.   
  135.             if (mOnItemDismissedListener != null) {  
  136.                 mOnItemDismissedListener.onDismiss(showcaseView, mSequencePosition);  
  137.             }  
  138.   
  139.             /** 
  140.              * If so, update the prefsManager so we can potentially resume this sequence in the future 
  141.              */  
  142.             if (mPrefsManager != null) {  
  143.                 mSequencePosition++;  
  144.                 mPrefsManager.setSequenceStatus(mSequencePosition);  
  145.             }  
  146.   
  147.             showNextItem();  
  148.         }  
  149.     }  
  150.   
  151.     public void setConfig(ShowcaseConfig config) {  
  152.         this.mConfig = config;  
  153.     }  
  154.   
  155.     public interface OnSequenceItemShownListener {  
  156.         void onShow(MaterialShowcaseView itemView, int position);  
  157.     }  
  158.   
  159.     public interface OnSequenceItemDismissedListener {  
  160.         void onDismiss(MaterialShowcaseView itemView, int position);  
  161.     }  
  162.   
  163. }  


10. 如今很流行的WebView方式实现,里面可以嵌入H5更炫丽的实现方式,很简单就是利用webview的loadUrl()方法。当然,我这里实现了两种方式,里面用回调来处理页面点击交互,使用方式就是实现OnOpenListener接口:

[java] view plaincopy
  1. @SuppressLint("SetJavaScriptEnabled")  
  2.     private void init_() {  
  3.         mWebSettings.setJavaScriptEnabled(true);  
  4.     }  
  5.       
  6.     @SuppressWarnings("deprecation")  
  7.     @SuppressLint({ "SetJavaScriptEnabled""NewApi" })  
  8.     private void init() {    
  9.         mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);    
  10.         mWebSettings.setUseWideViewPort(true);  
  11.             
  12.         /**   
  13.          * 用WebView显示图片,可使用这个参数 设置网页布局类型: 1、LayoutAlgorithm.NARROW_COLUMNS :   
  14.          * 适应内容大小 2、LayoutAlgorithm.SINGLE_COLUMN:适应屏幕,内容将自动缩放   
  15.          */    
  16.         mWebSettings.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN);    
  17.                 
  18.         mWebSettings.setDisplayZoomControls(false);    
  19.         mWebSettings.setJavaScriptEnabled(true); // 设置支持javascript脚本    
  20.         mWebSettings.setAllowFileAccess(true); // 允许访问文件    
  21.         mWebSettings.setBuiltInZoomControls(true); // 设置显示缩放按钮    
  22.         mWebSettings.setSupportZoom(false); // 不支持缩放    
  23.   
  24.         mWebSettings.setLoadWithOverviewMode(true);    
  25.             
  26.         DisplayMetrics metrics = new DisplayMetrics();    
  27.         ((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(metrics);    
  28.           int mDensity = metrics.densityDpi;    
  29.           if (mDensity == 240) {     
  30.            mWebSettings.setDefaultZoom(ZoomDensity.FAR);    
  31.           } else if (mDensity == 160) {    
  32.              mWebSettings.setDefaultZoom(ZoomDensity.MEDIUM);    
  33.           } else if(mDensity == 120) {    
  34.            mWebSettings.setDefaultZoom(ZoomDensity.CLOSE);    
  35.           }else if(mDensity == DisplayMetrics.DENSITY_XHIGH){    
  36.            mWebSettings.setDefaultZoom(ZoomDensity.FAR);     
  37.           }else if (mDensity == DisplayMetrics.DENSITY_TV){    
  38.            mWebSettings.setDefaultZoom(ZoomDensity.FAR);     
  39.           }else{    
  40.               mWebSettings.setDefaultZoom(ZoomDensity.MEDIUM);    
  41.           }    
  42.   
  43.     }  
  44.       
  45.     public void loadLocalHtml(String assetUrl) {  
  46.         init_();  
  47.         mWebView.setWebViewClient(new MyWebViewClient());  
  48.         mWebView.loadUrl(assetUrl);  
  49.     }  
  50.       
  51.     public void loadHtml(String htmlData) {  
  52.         init();  
  53.         mWebView.setWebViewClient(new MyWebViewClient());  
  54.         mWebView.loadDataWithBaseURL("about:blank", htmlData, "text/html""utf-8"null);  
  55.     }  
  56.       
  57.     public void setOnOpenListener(OnOpenListener onOpenListener){  
  58.         this.mOnOpenListener = onOpenListener;  
  59.     }  
  60.       
  61.     public interface OnOpenListener {  
  62.         void onOpen(String url);  
  63.           
  64.           
  65.     }  
  66.       
  67.     class MyWebViewClient extends WebViewClient{  
  68.   
  69.         @Override  
  70.         public boolean shouldOverrideUrlLoading(WebView view, String url) {  
  71.             if (mOnOpenListener != null) {  
  72.                 mOnOpenListener.onOpen(url);  
  73.             }  
  74.             return true;  
  75.         }  
  76.           
  77.     }  

OK,如果有更好的实现方式或者不足的指出,还请在评论区交流,共同学习,共同进步!

Demo下载

2 0
原创粉丝点击