国庆放假玩疯了,一直没静下心来,还有就是没找到更好地解决方式,自己琢磨了几个解决方法,大神勿喷.
写在前面
不知道大牛们怎么实现的,自己尝试鼓捣了几个方式,有的并不可取(源代码注释有说明),只作为知识点掌握吧。
本次之所以用Android 5.0开发完成是因为有一个很好的demo(github上)是建立在此基础上的,不过你可以稍微修改就能向下兼容,我做了注释,我集成在本Demo中是倒数第二个。由于是整合,里面的资源文件自然都写在一起了,如果想分离某一种实现方式还请看懂Demo逐一分离,根据Demo分离所依赖的资源。另外,没有加入内部存储,不过包含了这个工具类,为了演示效果没有往具体功能里面添加相关判断是不是首次引导的代码,这个在跳转成功的那个连接或者按钮提示处添加就行啦,比较简单。
简单解释
单独拿出了主文件和跳转成功页面,每个包是一种实现方式,里面有包含Activity。这是src
布局中activity_是上面说的单独拿出的两个activity,layout_都是分别实现的布局文件,shade_是倒数第二种大牛实现的方式的activity。这是布局。
关于资源文件分离要慎重,这是res,慎重哟~ 就是这样
实现说明
1. Splash实现方式
说白了就是利用Handler的延迟功能,核心代码如下:
/** * 采用Splash延迟启动方式,实现方式就是通过handler延迟启动,splash显示的就是一个activity里面一张背景图 * 采用这个方式将要花费一个activity消耗太大,采用visible设置显示或隐藏会更好。 * */ private void splashShow() { AnimationUtil.activityZoomAnimation(this); new Handler().postDelayed(new Runnable() { @Override public void run() { startActivity(new Intent(SplashActivity.this, SuessedActivity.class)); AnimationUtil.finishActivityAnimation(SplashActivity.this); } }, Constant.DELAY); }
借助V4包下的ViewPage组件实现,这里实现两种方式,第二种是仿知乎的。核心是
private void pageGuide() { ImageView imageView = null; guides.clear(); //添加引导页 for (int i = 0; i < ids.length; i++) { imageView = creatImageView(ids[i]); guides.add(imageView); } // 当mCurDotImageView的所在的树形层次将要被绘出时此方法被调用 mCurDotImageView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() { @Override public boolean onPreDraw() { offset = mCurDotImageView.getWidth(); return true; } }); myGuidePageAdapter = new MyGuidePageAdapter(guides); mViewPager.setAdapter(myGuidePageAdapter); mViewPager.clearAnimation(); // 添加页面滑动监听 mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){ @Override public void onPageSelected(int position) { int pos = position % ids.length; moveCursorTo(pos); if (pos == ids.length-1) {// 到最后一张了 handler.sendEmptyMessageDelayed(TO_THE_END, 500); } else if (curPos == ids.length - 1) { handler.sendEmptyMessageDelayed(LEAVE_FROM_END, 100); } curPos = pos; super.onPageSelected(position); } }); }
private void moveCursorTo(int pos) { AnimationSet animationSet = new AnimationSet(true); TranslateAnimation tAnim = new TranslateAnimation(offset * curPos, offset * pos, 0, 0); animationSet.addAnimation(tAnim); animationSet.setDuration(300); animationSet.setFillAfter(true); mCurDotImageView.startAnimation(animationSet); } private ImageView creatImageView(int id){ ImageView iv = new ImageView(this); iv.setImageResource(id); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); iv.setLayoutParams(params); iv.setScaleType(ScaleType.FIT_XY); return iv; }
public void onScrollChanged(int top, int oldTop) { int animTop = mLinearLayout.getTop() - top; if (top > oldTop) { if (animTop < mStartAnimateTop && !hasStart) { Animation anim = AnimationUtils .loadAnimation(this, R.anim.show); mLinearLayout.setVisibility(View.VISIBLE); mLinearLayout.startAnimation(anim); hasStart = true; } } else { if (animTop > mStartAnimateTop && hasStart) { Animation anim = AnimationUtils.loadAnimation(this, R.anim.close); mLinearLayout.setVisibility(View.INVISIBLE); mLinearLayout.startAnimation(anim); hasStart = false; } } }
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="我是新功能"/> <RelativeLayout android:id="@+id/tip_rl" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/g_tips"> </RelativeLayout> </RelativeLayout>6. WindowManager实现【 不推荐使用】
因为它是系统级别的Window,是整个OS唯一的Window,所以出现引导按home键会有问题,可以利用它实现悬浮框,比如360的小球。核心如下,通过addView(),removeView();实现
private void initParam() { params = new LayoutParams(); // 类型 params.type = LayoutParams.TYPE_SYSTEM_ALERT; // flags params.flags = LayoutParams.FLAG_ALT_FOCUSABLE_IM; // 如果设置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,弹出的View收不到Back键的事件 // 格式(半透明) params.format = PixelFormat.TRANSLUCENT; params.width = LayoutParams.MATCH_PARENT; params.height = LayoutParams.MATCH_PARENT; } /** * @param view 遮罩层view * @param x * @param y 位置 * */ public void display(int layoutId) { initParam(); final View view = LayoutInflater.from(context).inflate(layoutId, null); mWindowManager.addView(view, params); view.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (view.getParent() != null) { mWindowManager.removeView(view); } } });
public void display() { ViewParent viewParent = rootView.getParent(); if (viewParent instanceof FrameLayout) { if (length > 0 && current < length) { final FrameLayout container = (FrameLayout) viewParent; final ImageView imageView = new ImageView(activity); imageView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); imageView.setBackgroundResource(coverIds[current]); imageView.setScaleType(ScaleType.FIT_XY); imageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { container.removeView(imageView); handler.sendEmptyMessage(CONTINUE); } }); current ++ ; container.addView(imageView); } } }
@SuppressWarnings("deprecation") public void init(int layoutId) { View view = LayoutInflater.from(context).inflate(layoutId, null); this.setOutsideTouchable(true); this.setBackgroundDrawable(new BitmapDrawable()); this.setHeight(LayoutParams.MATCH_PARENT); this.setWidth(LayoutParams.MATCH_PARENT); this.setFocusable(true); this.setContentView(view); this.setAnimationStyle(R.style.dialog_animation); view.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { popuWindowHelper.dismiss(); } }); }
9. 自定义View实现【大牛】,值得推荐
其核心就是自定义一个View并添加点击事件监听,为了做到连贯性,采用一个suquence队列显示需要引导的item,用这个队列管理引导步骤,如果是单纯的一个指示的话就用不到该队列了。。
package com.lzy.shadeguide.child.shade.way_three.thrid_lib; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.os.Build; import android.os.Handler; import android.text.TextUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.TextView; import java.util.ArrayList; import java.util.List; import com.lzy.shadeguide.R; import com.lzy.shadeguide.child.shade.way_three.thrid_lib.shape.CircleShape; import com.lzy.shadeguide.child.shade.way_three.thrid_lib.shape.NoShape; import com.lzy.shadeguide.child.shade.way_three.thrid_lib.shape.RectangleShape; import com.lzy.shadeguide.child.shade.way_three.thrid_lib.shape.Shape; import com.lzy.shadeguide.child.shade.way_three.thrid_lib.target.Target; import com.lzy.shadeguide.child.shade.way_three.thrid_lib.target.ViewTarget; /** * Helper class to show a sequence of showcase views. */ public class MaterialShowcaseView extends FrameLayout implements View.OnTouchListener, View.OnClickListener { private int mOldHeight; private int mOldWidth; private Bitmap mBitmap;// = new WeakReference<>(null); private Canvas mCanvas; private Paint mEraser; private Target mTarget; private Shape mShape; private int mXPosition; private int mYPosition; private boolean mWasDismissed = false; private int mShapePadding = ShowcaseConfig.DEFAULT_SHAPE_PADDING; private View mContentBox; private TextView mContentTextView; private TextView mDismissButton; private int mGravity; private int mContentBottomMargin; private int mContentTopMargin; private boolean mDismissOnTouch = false; private boolean mShouldRender = false; // flag to decide when we should actually render private int mMaskColour; private AnimationFactory mAnimationFactory; private boolean mShouldAnimate = true; private long mFadeDurationInMillis = ShowcaseConfig.DEFAULT_FADE_TIME; private Handler mHandler; private long mDelayInMillis = ShowcaseConfig.DEFAULT_DELAY; private int mBottomMargin = 0; private boolean mSingleUse = false; // should display only once private PrefsManager mPrefsManager; // used to store state doe single use mode List<IShowcaseListener> mListeners; // external listeners who want to observe when we show and dismiss private UpdateOnGlobalLayout mLayoutListener; private IDetachedListener mDetachedListener; public MaterialShowcaseView(Context context) { super(context); init(context); } public MaterialShowcaseView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public MaterialShowcaseView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) //Android 5.0 public MaterialShowcaseView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context); } private void init(Context context) { setWillNotDraw(false); // create our animation factory mAnimationFactory = new AnimationFactory(); mListeners = new ArrayList<IShowcaseListener>(); // make sure we add a global layout listener so we can adapt to changes mLayoutListener = new UpdateOnGlobalLayout(); getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener); // consume touch events setOnTouchListener(this); mMaskColour = Color.parseColor(ShowcaseConfig.DEFAULT_MASK_COLOUR); setVisibility(INVISIBLE); View contentView = LayoutInflater.from(getContext()).inflate(R.layout.shade_showcase_content, this, true); mContentBox = contentView.findViewById(R.id.content_box); mContentTextView = (TextView) contentView.findViewById(R.id.tv_content); mDismissButton = (TextView) contentView.findViewById(R.id.tv_dismiss); mDismissButton.setOnClickListener(this); } /** * Interesting drawing stuff. * We draw a block of semi transparent colour to fill the whole screen then we draw of transparency * to create a circular "viewport" through to the underlying content * * @param canvas */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // don't bother drawing if we're not ready if (!mShouldRender) return; // get current dimensions int width = getMeasuredWidth(); int height = getMeasuredHeight(); // build a new canvas if needed i.e first pass or new dimensions if (mBitmap == null || mCanvas == null || mOldHeight != height || mOldWidth != width) { if (mBitmap != null) mBitmap.recycle(); mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mBitmap); } // save our 'old' dimensions mOldWidth = width; mOldHeight = height; // clear canvas mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); // draw solid background mCanvas.drawColor(mMaskColour); // Prepare eraser Paint if needed if (mEraser == null) { mEraser = new Paint(); mEraser.setColor(0xFFFFFFFF); mEraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); mEraser.setFlags(Paint.ANTI_ALIAS_FLAG); } // draw (erase) shape mShape.draw(mCanvas, mEraser, mXPosition, mYPosition, mShapePadding); // Draw the bitmap on our views canvas. canvas.drawBitmap(mBitmap, 0, 0, null); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); /** * If we're being detached from the window without the mWasDismissed flag then we weren't purposefully dismissed * Probably due to an orientation change or user backed out of activity. * Ensure we reset the flag so the showcase display again. */ if (!mWasDismissed && mSingleUse && mPrefsManager != null) { mPrefsManager.resetShowcase(); } notifyOnDismissed(); } @Override public boolean onTouch(View v, MotionEvent event) { if (mDismissOnTouch) { hide(); } return true; } private void notifyOnDisplayed() { for (IShowcaseListener listener : mListeners) { listener.onShowcaseDisplayed(this); } } private void notifyOnDismissed() { if (mListeners != null) { for (IShowcaseListener listener : mListeners) { listener.onShowcaseDismissed(this); } mListeners.clear(); mListeners = null; } /** * internal listener used by sequence for storing progress within the sequence */ if (mDetachedListener != null) { mDetachedListener.onShowcaseDetached(this, mWasDismissed); } } /** * Dismiss button clicked * * @param v */ @Override public void onClick(View v) { hide(); } /** * Tells us about the "Target" which is the view we want to anchor to. * We figure out where it is on screen and (optionally) how big it is. * We also figure out whether to place our content and dismiss button above or below it. * * @param target */ public void setTarget(Target target) { mTarget = target; // update dismiss button state updateDismissButton(); if (mTarget != null) { /** * If we're on lollipop then make sure we don't draw over the nav bar */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mBottomMargin = getSoftButtonsBarSizePort((Activity) getContext()); FrameLayout.LayoutParams contentLP = (LayoutParams) getLayoutParams(); if (contentLP != null && contentLP.bottomMargin != mBottomMargin) contentLP.bottomMargin = mBottomMargin; } // apply the target position Point targetPoint = mTarget.getPoint(); Rect targetBounds = mTarget.getBounds(); setPosition(targetPoint); // now figure out whether to put content above or below it int height = getMeasuredHeight(); int midPoint = height / 2; int yPos = targetPoint.y; int radius = Math.max(targetBounds.height(), targetBounds.width()) / 2; if (mShape != null) { mShape.updateTarget(mTarget); radius = mShape.getHeight() / 2; } if (yPos > midPoint) { // target is in lower half of screen, we'll sit above it mContentTopMargin = 0; mContentBottomMargin = (height - yPos) + radius + mShapePadding; mGravity = Gravity.BOTTOM; } else { // target is in upper half of screen, we'll sit below it mContentTopMargin = yPos + radius + mShapePadding; mContentBottomMargin = 0; mGravity = Gravity.TOP; } } applyLayoutParams(); } private void applyLayoutParams() { if (mContentBox != null && mContentBox.getLayoutParams() != null) { FrameLayout.LayoutParams contentLP = (LayoutParams) mContentBox.getLayoutParams(); boolean layoutParamsChanged = false; if (contentLP.bottomMargin != mContentBottomMargin) { contentLP.bottomMargin = mContentBottomMargin; layoutParamsChanged = true; } if (contentLP.topMargin != mContentTopMargin) { contentLP.topMargin = mContentTopMargin; layoutParamsChanged = true; } if (contentLP.gravity != mGravity) { contentLP.gravity = mGravity; layoutParamsChanged = true; } /** * Only apply the layout params if we've actually changed them, otherwise we'll get stuck in a layout loop */ if (layoutParamsChanged) mContentBox.setLayoutParams(contentLP); } } /** * SETTERS */ void setPosition(Point point) { setPosition(point.x, point.y); } void setPosition(int x, int y) { mXPosition = x; mYPosition = y; } private void setContentText(CharSequence contentText) { if (mContentTextView != null) { mContentTextView.setText(contentText); } } private void setDismissText(CharSequence dismissText) { if (mDismissButton != null) { mDismissButton.setText(dismissText); updateDismissButton(); } } private void setContentTextColor(int textColour) { if (mContentTextView != null) { mContentTextView.setTextColor(textColour); } } private void setDismissTextColor(int textColour) { if (mDismissButton != null) { mDismissButton.setTextColor(textColour); } } private void setShapePadding(int padding) { mShapePadding = padding; } private void setDismissOnTouch(boolean dismissOnTouch) { mDismissOnTouch = dismissOnTouch; } private void setShouldRender(boolean shouldRender) { mShouldRender = shouldRender; } private void setMaskColour(int maskColour) { mMaskColour = maskColour; } private void setDelay(long delayInMillis) { mDelayInMillis = delayInMillis; } private void setFadeDuration(long fadeDurationInMillis) { mFadeDurationInMillis = fadeDurationInMillis; } public void addShowcaseListener(IShowcaseListener showcaseListener) { mListeners.add(showcaseListener); } public void removeShowcaseListener(MaterialShowcaseSequence showcaseListener) { if (mListeners.contains(showcaseListener)) { mListeners.remove(showcaseListener); } } void setDetachedListener(IDetachedListener detachedListener) { mDetachedListener = detachedListener; } public void setShape(Shape mShape) { this.mShape = mShape; } /** * Set properties based on a config object * * @param config */ public void setConfig(ShowcaseConfig config) { setDelay(config.getDelay()); setFadeDuration(config.getFadeDuration()); setContentTextColor(config.getContentTextColor()); setDismissTextColor(config.getDismissTextColor()); setMaskColour(config.getMaskColor()); setShape(config.getShape()); setShapePadding(config.getShapePadding()); } private void updateDismissButton() { // hide or show button if (mDismissButton != null) { if (TextUtils.isEmpty(mDismissButton.getText())) { mDismissButton.setVisibility(GONE); } else { mDismissButton.setVisibility(VISIBLE); } } } public boolean hasFired() { return mPrefsManager.hasFired(); } /** * REDRAW LISTENER - this ensures we redraw after activity finishes laying out */ private class UpdateOnGlobalLayout implements ViewTreeObserver.OnGlobalLayoutListener { @Override public void onGlobalLayout() { setTarget(mTarget); } } /** * BUILDER CLASS * Gives us a builder utility class with a fluent API for eaily configuring showcase views */ public static class Builder { private static final int CIRCLE_SHAPE = 0; private static final int RECTANGLE_SHAPE = 1; private static final int NO_SHAPE = 2; private boolean fullWidth = false; private int shapeType = CIRCLE_SHAPE; final MaterialShowcaseView showcaseView; private final Activity activity; public Builder(Activity activity) { this.activity = activity; showcaseView = new MaterialShowcaseView(activity); } /** * Set the title text shown on the ShowcaseView. */ public Builder setTarget(View target) { showcaseView.setTarget(new ViewTarget(target)); return this; } /** * Set the title text shown on the ShowcaseView. */ public Builder setDismissText(int resId) { return setDismissText(activity.getString(resId)); } public Builder setDismissText(CharSequence dismissText) { showcaseView.setDismissText(dismissText); return this; } /** * Set the title text shown on the ShowcaseView. */ public Builder setContentText(int resId) { return setContentText(activity.getString(resId)); } /** * Set the descriptive text shown on the ShowcaseView. */ public Builder setContentText(CharSequence text) { showcaseView.setContentText(text); return this; } public Builder setDismissOnTouch(boolean dismissOnTouch) { showcaseView.setDismissOnTouch(dismissOnTouch); return this; } public Builder setMaskColour(int maskColour) { showcaseView.setMaskColour(maskColour); return this; } public Builder setContentTextColor(int textColour) { showcaseView.setContentTextColor(textColour); return this; } public Builder setDismissTextColor(int textColour) { showcaseView.setDismissTextColor(textColour); return this; } public Builder setDelay(int delayInMillis) { showcaseView.setDelay(delayInMillis); return this; } public Builder setFadeDuration(int fadeDurationInMillis) { showcaseView.setFadeDuration(fadeDurationInMillis); return this; } public Builder setListener(IShowcaseListener listener) { showcaseView.addShowcaseListener(listener); return this; } public Builder singleUse(String showcaseID) { showcaseView.singleUse(showcaseID); return this; } public Builder setShape(Shape shape) { showcaseView.setShape(shape); return this; } public Builder withCircleShape() { shapeType = CIRCLE_SHAPE; return this; } public Builder withoutShape() { shapeType = NO_SHAPE; return this; } public Builder setShapePadding(int padding) { showcaseView.setShapePadding(padding); return this; } public Builder withRectangleShape() { return withRectangleShape(false); } public Builder withRectangleShape(boolean fullWidth) { this.shapeType = RECTANGLE_SHAPE; this.fullWidth = fullWidth; return this; } public MaterialShowcaseView build() { if (showcaseView.mShape == null) { switch (shapeType) { case RECTANGLE_SHAPE: { showcaseView.setShape(new RectangleShape(showcaseView.mTarget.getBounds(), fullWidth)); break; } case CIRCLE_SHAPE: { showcaseView.setShape(new CircleShape(showcaseView.mTarget)); break; } case NO_SHAPE: { showcaseView.setShape(new NoShape()); break; } default: throw new IllegalArgumentException("Unsupported shape type: " + shapeType); } } return showcaseView; } public MaterialShowcaseView show() { build().show(activity); return showcaseView; } } private void singleUse(String showcaseID) { mSingleUse = true; mPrefsManager = new PrefsManager(getContext(), showcaseID); } @SuppressWarnings("deprecation") public void removeFromWindow() { if (getParent() != null && getParent() instanceof ViewGroup) { ((ViewGroup) getParent()).removeView(this); } if (mBitmap != null) { mBitmap.recycle(); mBitmap = null; } mEraser = null; mAnimationFactory = null; mCanvas = null; mHandler = null; getViewTreeObserver().removeGlobalOnLayoutListener(mLayoutListener); mLayoutListener = null; if (mPrefsManager != null) mPrefsManager.close(); mPrefsManager = null; } /** * Reveal the showcaseview. Returns a boolean telling us whether we actually did show anything * * @param activity * @return */ public boolean show(final Activity activity) { /** * if we're in single use mode and have already shot our bolt then do nothing */ if (mSingleUse) { if (mPrefsManager.hasFired()) { return false; } else { mPrefsManager.setFired(); } } ((ViewGroup) activity.getWindow().getDecorView()).addView(this); setShouldRender(true); mHandler = new Handler(); mHandler.postDelayed(new Runnable() { @Override public void run() { if (mShouldAnimate) { fadeIn(); } else { setVisibility(VISIBLE); notifyOnDisplayed(); } } }, mDelayInMillis); updateDismissButton(); return true; } public void hide() { /** * This flag is used to indicate to onDetachedFromWindow that the showcase view was dismissed purposefully (by the user or programmatically) */ mWasDismissed = true; if (mShouldAnimate) { fadeOut(); } else { removeFromWindow(); } } public void fadeIn() { setVisibility(INVISIBLE); mAnimationFactory.fadeInView(this, mFadeDurationInMillis, new IAnimationFactory.AnimationStartListener() { @Override public void onAnimationStart() { setVisibility(View.VISIBLE); notifyOnDisplayed(); } } ); } public void fadeOut() { mAnimationFactory.fadeOutView(this, mFadeDurationInMillis, new IAnimationFactory.AnimationEndListener() { @Override public void onAnimationEnd() { setVisibility(INVISIBLE); removeFromWindow(); } }); } public void resetSingleUse() { if (mSingleUse && mPrefsManager != null) mPrefsManager.resetShowcase(); } /** * Static helper method for resetting single use flag * * @param context * @param showcaseID */ public static void resetSingleUse(Context context, String showcaseID) { PrefsManager.resetShowcase(context, showcaseID); } /** * Static helper method for resetting all single use flags * * @param context */ public static void resetAll(Context context) { PrefsManager.resetAll(context); } @SuppressLint("NewApi") public static int getSoftButtonsBarSizePort(Activity activity) { // getRealMetrics is only available with API 17 and + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { DisplayMetrics metrics = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); int usableHeight = metrics.heightPixels; activity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics); int realHeight = metrics.heightPixels; if (realHeight > usableHeight) return realHeight - usableHeight; else return 0; } return 0; } }
package com.lzy.shadeguide.child.shade.way_three.thrid_lib; import android.app.Activity; import android.view.View; import java.util.LinkedList; import java.util.Queue; public class MaterialShowcaseSequence implements IDetachedListener { PrefsManager mPrefsManager; Queue<MaterialShowcaseView> mShowcaseQueue; private boolean mSingleUse = false; Activity mActivity; private ShowcaseConfig mConfig; private int mSequencePosition = 0; private OnSequenceItemShownListener mOnItemShownListener = null; private OnSequenceItemDismissedListener mOnItemDismissedListener = null; public MaterialShowcaseSequence(Activity activity) { mActivity = activity; mShowcaseQueue = new LinkedList<MaterialShowcaseView>(); } public MaterialShowcaseSequence(Activity activity, String sequenceID) { this(activity); this.singleUse(sequenceID); } public MaterialShowcaseSequence addSequenceItem(View targetView, String content, String dismissText) { MaterialShowcaseView sequenceItem = new MaterialShowcaseView.Builder(mActivity) .setTarget(targetView) .setDismissText(dismissText) .setContentText(content) .build(); if (mConfig != null) { sequenceItem.setConfig(mConfig); } mShowcaseQueue.add(sequenceItem); return this; } public MaterialShowcaseSequence addSequenceItem(MaterialShowcaseView sequenceItem) { mShowcaseQueue.add(sequenceItem); return this; } public MaterialShowcaseSequence singleUse(String sequenceID) { mSingleUse = true; mPrefsManager = new PrefsManager(mActivity, sequenceID); return this; } public void setOnItemShownListener(OnSequenceItemShownListener listener) { this.mOnItemShownListener = listener; } public void setOnItemDismissedListener(OnSequenceItemDismissedListener listener) { this.mOnItemDismissedListener = listener; } public boolean hasFired() { if (mPrefsManager.getSequenceStatus() == PrefsManager.SEQUENCE_FINISHED) { return true; } return false; } public void start() { /** * Check if we've already shot our bolt and bail out if so * */ if (mSingleUse) { if (hasFired()) { return; } /** * See if we have started this sequence before, if so then skip to the point we reached before * instead of showing the user everything from the start */ mSequencePosition = mPrefsManager.getSequenceStatus(); if (mSequencePosition > 0) { for (int i = 0; i < mSequencePosition; i++) { mShowcaseQueue.poll(); } } } // do start if (mShowcaseQueue.size() > 0) showNextItem(); } private void showNextItem() { if (mShowcaseQueue.size() > 0 && !mActivity.isFinishing()) { MaterialShowcaseView sequenceItem = mShowcaseQueue.remove(); sequenceItem.setDetachedListener(this); sequenceItem.show(mActivity); if (mOnItemShownListener != null) { mOnItemShownListener.onShow(sequenceItem, mSequencePosition); } } else { /** * We've reached the end of the sequence, save the fired state */ if (mSingleUse) { mPrefsManager.setFired(); } } } @Override public void onShowcaseDetached(MaterialShowcaseView showcaseView, boolean wasDismissed) { showcaseView.setDetachedListener(null); /** * We're only interested if the showcase was purposefully dismissed */ if (wasDismissed) { if (mOnItemDismissedListener != null) { mOnItemDismissedListener.onDismiss(showcaseView, mSequencePosition); } /** * If so, update the prefsManager so we can potentially resume this sequence in the future */ if (mPrefsManager != null) { mSequencePosition++; mPrefsManager.setSequenceStatus(mSequencePosition); } showNextItem(); } } public void setConfig(ShowcaseConfig config) { this.mConfig = config; } public interface OnSequenceItemShownListener { void onShow(MaterialShowcaseView itemView, int position); } public interface OnSequenceItemDismissedListener { void onDismiss(MaterialShowcaseView itemView, int position); } }
10. 如今很流行的WebView方式实现,里面可以嵌入H5更炫丽的实现方式,很简单就是利用webview的loadUrl()方法。当然,我这里实现了两种方式,里面用回调来处理页面点击交互,使用方式就是实现OnOpenListener接口:
@SuppressLint("SetJavaScriptEnabled") private void init_() { mWebSettings.setJavaScriptEnabled(true); } @SuppressWarnings("deprecation") @SuppressLint({ "SetJavaScriptEnabled", "NewApi" }) private void init() { mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true); mWebSettings.setUseWideViewPort(true); /** * 用WebView显示图片,可使用这个参数 设置网页布局类型: 1、LayoutAlgorithm.NARROW_COLUMNS : * 适应内容大小 2、LayoutAlgorithm.SINGLE_COLUMN:适应屏幕,内容将自动缩放 */ mWebSettings.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); mWebSettings.setDisplayZoomControls(false); mWebSettings.setJavaScriptEnabled(true); // 设置支持javascript脚本 mWebSettings.setAllowFileAccess(true); // 允许访问文件 mWebSettings.setBuiltInZoomControls(true); // 设置显示缩放按钮 mWebSettings.setSupportZoom(false); // 不支持缩放 mWebSettings.setLoadWithOverviewMode(true); DisplayMetrics metrics = new DisplayMetrics(); ((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(metrics); int mDensity = metrics.densityDpi; if (mDensity == 240) { mWebSettings.setDefaultZoom(ZoomDensity.FAR); } else if (mDensity == 160) { mWebSettings.setDefaultZoom(ZoomDensity.MEDIUM); } else if(mDensity == 120) { mWebSettings.setDefaultZoom(ZoomDensity.CLOSE); }else if(mDensity == DisplayMetrics.DENSITY_XHIGH){ mWebSettings.setDefaultZoom(ZoomDensity.FAR); }else if (mDensity == DisplayMetrics.DENSITY_TV){ mWebSettings.setDefaultZoom(ZoomDensity.FAR); }else{ mWebSettings.setDefaultZoom(ZoomDensity.MEDIUM); } } public void loadLocalHtml(String assetUrl) { init_(); mWebView.setWebViewClient(new MyWebViewClient()); mWebView.loadUrl(assetUrl); } public void loadHtml(String htmlData) { init(); mWebView.setWebViewClient(new MyWebViewClient()); mWebView.loadDataWithBaseURL("about:blank", htmlData, "text/html", "utf-8", null); } public void setOnOpenListener(OnOpenListener onOpenListener){ this.mOnOpenListener = onOpenListener; } public interface OnOpenListener { void onOpen(String url); } class MyWebViewClient extends WebViewClient{ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (mOnOpenListener != null) { mOnOpenListener.onOpen(url); } return true; } }
Demo下载:戳我
2015.10.19 修复一条bug
Demo下载:戳我
—— lovey hy.