未读消息-小红点 Android 实现
Hello, 村长 😊
「码不停蹄」
1、更新日志
- 优化代码已知问题
- 新增小红点长按拖动
- 新增小红点位置自定义显示
- 可显示在任意 view 上
2、使用 & 效果
显示文字,无效果
private BadgeView mReadBadgeView;
private TextView mRead;mReadBadgeView = new BadgeView(getActivity());
mReadBadgeView.showBadgeView("+99", mRead);

小红点 + 自定义显示位置
mBadgeView = new BadgeView(getActivity(), GravityDirection.DIRECT_BOTTOM_RIGHT);mBadgeView.showBadgeView(mHead);

拖动效果 + 自定义位置显示
mBadgeView = new BadgeView(getActivity(),GravityDirection.DIRECT_BOTTOM_RIGHT,true);mBadgeView.showBadgeView(mHead);
具有拖动效果
mBadgeView = new BadgeView(getActivity(), true);mBadgeView.showBadgeView(mHead);
3、代码实现
实现思路:利用 fragment Layout 容器的重叠特性,将小红点 view 添加到目标 view 上方。
3.1 自定义位置
import android.view.Gravity;import androidx.annotation.IntDef;@IntDef({GravityDirection.DIRECT_TOP_LEFT,GravityDirection.DIRECT_TOP_RIGHT,GravityDirection.DIRECT_BOTTOM_LEFT,GravityDirection.DIRECT_BOTTOM_RIGHT,GravityDirection.DIRECT_TOP_CENTER,GravityDirection.DIRECT_BOTTOM_CENTER,GravityDirection.DIRECT_LEFT_CENTER,GravityDirection.DIRECT_RIGHT_CENTER
})
public @interface GravityDirection {int DIRECT_TOP_LEFT = Gravity.TOP | Gravity.LEFT;int DIRECT_TOP_RIGHT = Gravity.TOP | Gravity.RIGHT;int DIRECT_BOTTOM_LEFT = Gravity.BOTTOM | Gravity.LEFT;int DIRECT_BOTTOM_RIGHT = Gravity.BOTTOM | Gravity.RIGHT;int DIRECT_TOP_CENTER = Gravity.TOP | Gravity.CENTER;int DIRECT_BOTTOM_CENTER = Gravity.BOTTOM | Gravity.CENTER;int DIRECT_LEFT_CENTER = Gravity.LEFT | Gravity.CENTER;int DIRECT_RIGHT_CENTER = Gravity.RIGHT | Gravity.CENTER;
}
3.2 圆形 drawable
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;public class CircleDrawable extends ShapeDrawable {private Paint mPaint;private int mRadio;public CircleDrawable(int radio, int painColor) {mPaint = new Paint();mPaint.setAntiAlias(true);mPaint.setColor(painColor);mRadio = radio;}@Overridepublic void draw(@NonNull Canvas canvas) {canvas.drawCircle(mRadio, mRadio, mRadio, mPaint);}@Overridepublic void setAlpha(@IntRange(from = 0, to = 255) int i) {mPaint.setAlpha(i);}@Overridepublic void setColorFilter(@Nullable ColorFilter colorFilter) {mPaint.setColorFilter(colorFilter);}@Overridepublic int getOpacity() {return PixelFormat.TRANSLUCENT;}/**** drawable实际宽高,圆形关键** @return*/@Overridepublic int getIntrinsicWidth() {return mRadio * 2;}@Overridepublic int getIntrinsicHeight() {return mRadio * 2;}
}
3.3 小红点
package com.primer.common.view;import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.DragEvent;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;import com.primer.common.constant.GravityDirection;
import com.primer.common.util.LogHelper;
import com.primer.common.view.drawable.CircleDrawable;public class BadgeView extends TextView {private final int DEFAULT_BADGE_RADIO = 15;private final int DEFAULT_TEXT_SIZE = 5;private final int DEFAULT_TEXT_COLOR = Color.WHITE;private final int DEFAULT_BADGE_COLOR = Color.RED;private final int DEFAULT_BADGE_GRAVITY = GravityDirection.DIRECT_TOP_RIGHT;private int mBadgeColor = DEFAULT_BADGE_COLOR;private int mBadgeRadio = DEFAULT_BADGE_RADIO;private int mBadgeGravity = DEFAULT_BADGE_GRAVITY;private int mTextColor = DEFAULT_TEXT_COLOR;private int mTextSize = DEFAULT_TEXT_SIZE;private String mText;private FrameLayout mFragmentLayout;private ViewGroup mTargetViewGroup;private Context mContext;private boolean mEnableDrag;private View mTargetView;private int mTargetWidth;private int mTargetHeight;public BadgeView(Context context) {super(context);init(context);}public BadgeView(Context context, @GravityDirection int badgeGravity) {super(context);init(context);customStyle(badgeGravity, false);}public BadgeView(Context context, boolean enableDrag) {super(context);init(context);customStyle(DEFAULT_BADGE_GRAVITY, enableDrag);}public BadgeView(Context context, @GravityDirection int badgeGravity, final boolean enableDrag) {super(context);init(context);customStyle(badgeGravity, enableDrag);}private void customStyle(@GravityDirection int badgeGravity, final boolean enableDrag) {mEnableDrag = enableDrag;mBadgeGravity = badgeGravity;setOnLongClickListener(new OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {if (enableDrag) {processDrag();}return true;}});}public BadgeView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init(context);}public BadgeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)public BadgeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);init(context);}private void init(Context context) {mFragmentLayout = new FrameLayout(context);mFragmentLayout.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));mContext = context;}private void processDrag() {ClipData.Item item = new ClipData.Item("");ClipData clipData = new ClipData("", new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}, item);DragShadowBuilder builder = new DragShadowBuilder(this);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {startDragAndDrop(clipData, builder, null, 0);} else {startDrag(clipData, builder, null, 0);}}/***** @param content* @param targetView* @param textColor* @param textSize* @param badgeColor* @param badgeRadio*/public void showBadgeView(String content, final View targetView, int textColor, int textSize, int badgeColor, int badgeRadio) {if (targetView == null) {throw new IllegalArgumentException("targetView view must not be null");}mTargetView = targetView;mBadgeRadio = badgeRadio;mTargetViewGroup = (ViewGroup) targetView.getParent();mTargetViewGroup.removeView(targetView);mTargetViewGroup.addView(mFragmentLayout, targetView.getLayoutParams());setTextColor(mTextColor);setTextSize(mTextSize);setGravity(Gravity.CENTER);if (content != null && content.length() <= 3) {mText = content;setText(content);}//文字和半径之间的适配if (content != null) {Rect rect = new Rect();this.getPaint().getTextBounds(content, 0, content.length(), rect);if (content.length() <= 3 && rect.width() >= mBadgeRadio) {mBadgeRadio = (rect.width() / 2) + 1;}}setVisibility(INVISIBLE);setBackground(getShapeDrawable());mFragmentLayout.addView(targetView);mFragmentLayout.addView(this);//直接获取 view 的宽高得到的数值均为 0 ,需要 post 包裹targetView.post(new Runnable() {@Overridepublic void run() {mTargetWidth = targetView.getWidth();mTargetHeight = targetView.getHeight();setVisibility(VISIBLE);processMargin();}});mTargetViewGroup.invalidate();}private void processMargin() {int left = 0, top = 0;switch (mBadgeGravity) {case GravityDirection.DIRECT_TOP_LEFT://defaultbreak;case GravityDirection.DIRECT_TOP_RIGHT:left = mTargetWidth - mBadgeRadio * 2;break;case GravityDirection.DIRECT_TOP_CENTER:left = mTargetWidth * 2 - mBadgeRadio;break;case GravityDirection.DIRECT_BOTTOM_LEFT:top = mTargetHeight - mBadgeRadio * 2;break;case GravityDirection.DIRECT_BOTTOM_RIGHT:top = mTargetHeight - mBadgeRadio * 2;left = mTargetWidth - mBadgeRadio * 2;break;case GravityDirection.DIRECT_BOTTOM_CENTER:left = mTargetWidth / 2 - mBadgeRadio;top = mTargetHeight - mBadgeRadio * 2;break;case GravityDirection.DIRECT_LEFT_CENTER:top = mTargetHeight / 2 - mBadgeRadio;break;case GravityDirection.DIRECT_RIGHT_CENTER:top = mTargetHeight / 2 - mBadgeRadio;left = mTargetWidth - mBadgeRadio * 2;break;default:break;}FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();layoutParams.setMargins(left, top, 0, 0);setLayoutParams(layoutParams);}private ShapeDrawable getShapeDrawable() {CircleDrawable drawable = new CircleDrawable(mBadgeRadio, mBadgeColor);return drawable;}/***** @param content* @param target*/public void showBadgeView(String content, View target) {showBadgeView(content, target,DEFAULT_TEXT_COLOR,DEFAULT_TEXT_SIZE,DEFAULT_BADGE_COLOR,DEFAULT_BADGE_RADIO);}public void showBadgeView(View target) {showBadgeView(null, target,DEFAULT_TEXT_COLOR,DEFAULT_TEXT_SIZE,DEFAULT_BADGE_COLOR,DEFAULT_BADGE_RADIO);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);}@Overridepublic boolean onDragEvent(DragEvent event) {if (!mEnableDrag) {return false;}LogHelper.d("drag event = " + event.getAction());return super.onDragEvent(event);}@Overridepublic boolean onTouchEvent(MotionEvent event) {if (!mEnableDrag) {return false;}LogHelper.d("touch event = " + event.getAction());switch (event.getAction()) {case MotionEvent.ACTION_MOVE:setVisibility(INVISIBLE);break;default:break;}return super.onTouchEvent(event);}
}
如有缺陷,欢迎评论(* ̄︶ ̄)
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
