我想知道是否有任何簡單的解決方案來創建一個元素將突出顯示的覆蓋。自定義指令覆蓋與突出顯示視圖(不使用ShowcaseViewLibrary)
所以,最終的結果會是這個樣子:
我想避免使用ShowcaseViewLibrary從種種理由(它不具有的外觀我需要的,它不再支持等)。
我想過使用FrameLayout,但我不確定如何實現突出顯示的現有元素。同時將箭頭或氣泡放在元素上,以便精確連接。
我想知道是否有任何簡單的解決方案來創建一個元素將突出顯示的覆蓋。自定義指令覆蓋與突出顯示視圖(不使用ShowcaseViewLibrary)
所以,最終的結果會是這個樣子:
我想避免使用ShowcaseViewLibrary從種種理由(它不具有的外觀我需要的,它不再支持等)。
我想過使用FrameLayout,但我不確定如何實現突出顯示的現有元素。同時將箭頭或氣泡放在元素上,以便精確連接。
一個快速簡便的方法是製作一個您想要演示的活動的副本,並添加疊加層並僅顯示該活動。這是我做的,它工作正常。
/**
* Created by Nikola D. on 10/1/2015.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class ShowCaseLayout extends ScrimInsetsFrameLayout {
private static final long DEFAULT_DURATION = 1000;
private static final int DEFAULT_RADIUS = 100;
private Paint mEmptyPaint;
private AbstractQueue<Pair<String, View>> mTargetQueue;
private int mLastCenterX = 600;
private int mLastCenterY = 100;
private ValueAnimator.AnimatorUpdateListener mAnimatorListenerX = new ValueAnimator.AnimatorUpdateListener() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mLastCenterX = (int) animation.getAnimatedValue();
setWillNotDraw(false);
postInvalidate();
}
};
private ValueAnimator.AnimatorUpdateListener mAnimatorListenerY = new ValueAnimator.AnimatorUpdateListener() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mLastCenterY = (int) animation.getAnimatedValue();
setWillNotDraw(false);
postInvalidate();
}
};
private ValueAnimator mCenterAnimatorX;
private ValueAnimator mCenterAnimatorY;
private boolean canRender = false;
private OnAttachStateChangeListener mAttachListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
canRender = true;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
@Override
public void onViewDetachedFromWindow(View v) {
canRender = false;
removeOnAttachStateChangeListener(this);
}
};
private long mDuration = DEFAULT_DURATION;
private int mRadius = (int) DEFAULT_RADIUS;
private Interpolator mInterpolator = new LinearOutSlowInInterpolator();
private ValueAnimator mRadiusAnimator;
private ValueAnimator.AnimatorUpdateListener mRadiusAnimatorListener = new ValueAnimator.AnimatorUpdateListener() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRadius = (int) animation.getAnimatedValue();
}
};
private TextView mDescriptionText;
private Button mGotItButton;
private OnClickListener mExternalGotItButtonlistener;
private OnClickListener mGotItButtonClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
setNextTarget();
if (mExternalGotItButtonlistener != null) {
mExternalGotItButtonlistener.onClick(v);
}
}
};
private Animator.AnimatorListener mAnimatorSetListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
setNextTarget();
invalidate();
//mDescriptionText.layout(mTempRect.left, mTempRect.bottom + mTempRect.bottom, mDescriptionText.);
}
};
private Rect mTempRect;
private Paint mBackgroundPaint;
private Bitmap bitmap;
private Canvas temp;
private int mStatusBarHeight = 0;
public ShowCaseLayout(Context context) {
super(context);
setupLayout();
}
public ShowCaseLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setupLayout();
}
public ShowCaseLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setupLayout();
}
public void setTarget(View target, String hint) {
mTargetQueue.add(new Pair<>(hint, target));
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void setupLayout() {
mTargetQueue = new LinkedBlockingQueue<>();
setWillNotDraw(false);
mBackgroundPaint = new Paint();
int c = Color.argb(127, Color.red(Color.RED), Color.blue(Color.RED), Color.green(Color.RED));
mBackgroundPaint.setColor(c);
mEmptyPaint = new Paint();
mEmptyPaint.setColor(Color.TRANSPARENT);
mEmptyPaint.setStyle(Paint.Style.FILL);
mEmptyPaint.setAntiAlias(true);
mEmptyPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
if (!ViewCompat.isLaidOut(this))
addOnAttachStateChangeListener(mAttachListener);
else canRender = true;
mDescriptionText = new TextView(getContext());
mGotItButton = new Button(getContext());
mGotItButton.setText("GOT IT");
mGotItButton.setOnClickListener(mGotItButtonClickListener);
addView(mGotItButton, generateDefaultLayoutParams());
//ViewCompat.setAlpha(this, 0.5f);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!canRender) return;
temp.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mBackgroundPaint);
temp.drawCircle(mLastCenterX, mLastCenterY, mRadius, mEmptyPaint);
canvas.drawBitmap(bitmap, 0, 0, null);
}
@TargetApi(Build.VERSION_CODES.M)
private void animateCenterToNextTarget(View target) {
int[] locations = new int[2];
target.getLocationInWindow(locations);
int x = locations[0];
int y = locations[1];
mTempRect = new Rect(x, y, x + target.getWidth(), y + target.getHeight());
int centerX = mTempRect.centerX();
int centerY = mTempRect.centerY();
int targetRadius = Math.abs(mTempRect.right - mTempRect.left)/2;
targetRadius += targetRadius * 0.05;
mCenterAnimatorX = ValueAnimator.ofInt(mLastCenterX, centerX).setDuration(mDuration);
mCenterAnimatorX.addUpdateListener(mAnimatorListenerX);
mCenterAnimatorY = ValueAnimator.ofInt(mLastCenterY, centerY).setDuration(mDuration);
mCenterAnimatorY.addUpdateListener(mAnimatorListenerY);
mRadiusAnimator = ValueAnimator.ofInt(mRadius, targetRadius);
mRadiusAnimator.addUpdateListener(mRadiusAnimatorListener);
playTogether(mCenterAnimatorY, mCenterAnimatorX, mRadiusAnimator);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
bitmap.eraseColor(Color.TRANSPARENT);
temp = new Canvas(bitmap);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void playTogether(ValueAnimator... animators) {
AnimatorSet set = new AnimatorSet();
set.setInterpolator(mInterpolator);
set.setDuration(mDuration);
set.playTogether(animators);
set.addListener(mAnimatorSetListener);
set.start();
}
public void start(Activity activity) {
if (getParent() == null) {
attachLayoutToWindow(activity);
}
setNextTarget();
}
private void setNextTarget() {
Pair<String, View> pair = mTargetQueue.poll();
if (pair != null) {
if (pair.second != null)
animateCenterToNextTarget(pair.second);
mDescriptionText.setText(pair.first);
}
}
private void attachLayoutToWindow(Activity activity) {
FrameLayout rootLayout = (FrameLayout) activity.findViewById(android.R.id.content);
rootLayout.addView(this);
}
public void hideShowcaseLayout() {
}
public void setGotItButtonClickistener(OnClickListener mExternalGotItButtonlistener) {
this.mExternalGotItButtonlistener = mExternalGotItButtonlistener;
}
public TextView getDescriptionTextView() {
return mDescriptionText;
}
public void setDescriptionTextView(TextView textView) {
mDescriptionText = textView;
}
}
請注意,此代碼是不完整的和正在開發中,你應該根據你的需要調整它。
此佈局將在其Rect
上圍繞View
繪製一個圓圈。
相反畫圓,你可以drawRect
到如果View的矩形目標視圖或drawRoundRect
和背景繪製Rect
的Rect
範圍是互補的。
繪製線(drawLine()
)應該從目標視圖:
startX = (rect.right - rect.left)/2;
startY = rect.bottom;
endX = startX;
endY = startY + arbitraryLineHeight;
如果恩迪比佈局高度你應該向上rect.top - arbitraryLineHeight
繪製它更大,否則在繪製時,因爲它是。
arbitraryLineHeight
可能是descriptionViewRect.top
這使得它更具動態性,而不是使用常數值。
你能告訴我「ScrimInsetsFrameLayout」從哪裏來? –
從設計支持庫,你沒有義務擴展它,你可以擴展到'FrameLayout' –