之前写了一个基础思路,实现了基础的功能,现在就需要让他可以露脸,提升颜值,提升作用,基础效果回顾:
进化~
evolve.gif
基本效果都实现了~ 代码 略有粗糙~ 进化版在基础上优化了
- 气泡的显示效果
- 动态设置属性,并增加了几个属性
- 添加到依附控件以及添加到屏幕,可以在整个屏幕内拖动
首先看怎么实现文字多的时候使用椭圆背景,
/**
* 测量文本占用大小
*/
private void measureText() {
mBadgeTextRect.left = 0;
mBadgeTextRect.top = 0;
if (TextUtils.isEmpty(mTextStr)) {
mBadgeTextRect.right = 0;
mBadgeTextRect.bottom = 0;
} else {
mBadgeTextPaint.setTextSize(mTextSize);
mBadgeTextRect.right = mBadgeTextPaint.measureText(mTextStr);
mBadgeTextFontMetrics = mBadgeTextPaint.getFontMetrics();
mBadgeTextRect.bottom = mBadgeTextFontMetrics.descent - mBadgeTextFontMetrics.ascent;
}
}
public void DrawBubMoveable(Canvas canvas, PointF center, float radius) {
if (mTextStr.isEmpty() || mTextStr.length() == 1) {
//数字为一位的时候
mBadgeBackgroundRect.left = center.x - (int) radius;
mBadgeBackgroundRect.top = center.y - (int) radius;
mBadgeBackgroundRect.right = center.x + (int) radius;
mBadgeBackgroundRect.bottom = center.y + (int) radius;
canvas.drawCircle(center.x, center.y, radius, mBubblePaint);
} else {
mBadgeBackgroundRect.left = center.x - (mBadgeTextRect.width() / 2f + mBadgePadding);
mBadgeBackgroundRect.top = center.y - (mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f);
mBadgeBackgroundRect.right = center.x + (mBadgeTextRect.width() / 2f + mBadgePadding);
mBadgeBackgroundRect.bottom = center.y + (mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f);
radius = mBadgeBackgroundRect.height() / 2f;
mBubMoveableRadius = radius;
canvas.drawRoundRect(mBadgeBackgroundRect, radius, radius, mBubblePaint);
}
if (!mTextStr.isEmpty()) {
canvas.drawText(mTextStr, center.x,
(mBadgeBackgroundRect.bottom + mBadgeBackgroundRect.top
- mBadgeTextFontMetrics.bottom - mBadgeTextFontMetrics.top) / 2f,
mBadgeTextPaint);
//基线中点
/*canvas.drawCircle(center.x,
(mBadgeBackgroundRect.bottom + mBadgeBackgroundRect.top) / 2 + (
mBadgeTextFontMetrics.bottom - mBadgeTextFontMetrics.top) / 2f - mBadgeTextFontMetrics.bottom, 10.0f,
mBadgeTextPaint);*/
}
}
首先需要计算文本大小,知道文本的左上右下角,Paint.FontMetrics 可以获得文字的 top,ascent,desent, bottom, leading这几个属性
text.pngbaseline以下是正值,以上是负值 descent - ascent 就是文字的高度了,基线的计算
baseline = center +(FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom
更多相关知识移步HenCoder Android 开发进阶:自定义 View 1-3 drawText() 文字的绘制
这里在文字长度大于2个的时候,计算一块区域,画椭圆就好~ 在计算椭圆半径的时候把mBubMoveableRadius,动圆的半径也赋值了,因为在画连接线的时候使用到了半径,值如果太大会超出气泡背景。其余页面优化就是在设置值后的,重新赋值,或者重新计算位置的逻辑。
new DragBubbleView(this)
.bindTarget(btn)
.setBadgeNumber(999)//设置数字
.setBadgeText("一二一")//设置文字
.setBadgeTextColor(getResources().getColor(R.color.colorAccent))//文字颜色
.setBadgeBackgroundColor(getResources().getColor(R.color.colorAccent))//背景颜色
.setBadgeTextSize(12, true)//设置文字大小
.setBadgePadding(5,true)//设置文字Padding
.setBadgeGravity(Gravity.BOTTOM|Gravity.START)//设置Gravity
.setGravityOffset(10,10,true)//设置偏移
.setBadgeBackgroundSize(12)//设置气泡半径 - - 别写太大,会有意想不到的效果
.setExactMode(true)//设置是否是精确值
.setOnDragStateChangedListener(new Badge.OnDragStateChangedListener() {
@Override
public void onDragStateChanged(int dragState, Badge badge, View targetView) {
switch (dragState) {
case BUBBLE_STATE_DISMISS:
badge.setBadgeNumber(badge.getBadgeNumber() + 1);
break;
}
}
});//设置监听才可以拖拽
嗯~ 属性设置最后就成了这样
在列表中使用的时候,为了防止复用导致的 重复显示,也可以使用xml初始化,然后对VIew进行显示隐藏,有人会问new出来的不能显示隐藏吗,亲测不行= =,原因还在寻找,如果有人遇到过同样的问题,还请指点一二~
<dragbubbleevolve.beyond.com.dragbubbleevolve.DragBubbleView
android:id="@+id/drag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<!--
app:bubble_color="#ff0000"
app:bubble_radius="12dp"
app:bubble_text="92"
app:bubble_textColor="#ffffff"
app:bubble_textSize="12sp"
-->
DragBubbleView dragBubbleView = helper.getView(R.id.drag);
if (item) {
dragBubbleView.setVisibility(View.VISIBLE);
dragBubbleView
.bindTarget(textView)
.setBadgeNumber(100+helper.getLayoutPosition())//设置数字
.setBadgeTextSize(10, true)//设置文字大小
.setBadgeBackgroundSize(10)
.setExactMode(true)//设置是否是精确值
.setOnDragStateChangedListener(new Badge.OnDragStateChangedListener() {
@Override
public void onDragStateChanged(int dragState, Badge badge, View targetView) {
switch (dragState) {
case BUBBLE_STATE_DISMISS:
getData().set(helper.getLayoutPosition(),false);
break;
}
}
});
} else {
dragBubbleView.setVisibility(View.GONE);
}
属性部分源码(链接在最下面)
体现了,注意重置各个属性值就行,得动态属性嘛~ 剩下的就是怎么添加在控件上,怎么将气泡移出本身的范围~ 这应该是这个自定义View的难点了吧~
@Override
public Badge bindTarget(final View targetView) {
if (targetView == null) {
throw new IllegalStateException("targetView can not be null");
}
if (getParent() != null) {
((ViewGroup) getParent()).removeView(this);
}
ViewParent targetParent = targetView.getParent();
if (targetParent != null && targetParent instanceof ViewGroup) {
mTargetView = targetView;
if (targetParent instanceof BadgeContainer) {
((BadgeContainer) targetParent).addView(this);
} else {
ViewGroup targetContainer = (ViewGroup) targetParent;
int index = targetContainer.indexOfChild(targetView);
ViewGroup.LayoutParams targetParams = targetView.getLayoutParams();
targetContainer.removeView(targetView);
final BadgeContainer badgeContainer = new BadgeContainer(getContext());
if (targetContainer instanceof RelativeLayout) {
badgeContainer.setId(targetView.getId());
}
targetContainer.addView(badgeContainer, index, targetParams);
badgeContainer.addView(targetView);
badgeContainer.addView(this);
}
} else {
throw new IllegalStateException("targetView must have a parent");
}
return this;
}
先看这段,我相信很多类型的控件这段代码是经常见到的,首先BadgeContainer是一个自定义ViewGroup,他会添加把依附控件和自定义View添加到自身,并通过子View计算所占大小,并通过计算除的宽高来测量我们的自定义View。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View targetView = null, badgeView = null;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (!(child instanceof DragBubbleView)) {
//拿到依附控件
targetView = child;
} else {
badgeView = child;
}
}
if (targetView == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
targetView.measure(widthMeasureSpec, heightMeasureSpec);
if (badgeView != null) {
badgeView.measure(MeasureSpec.makeMeasureSpec(targetView.getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(targetView.getMeasuredHeight(), MeasureSpec.EXACTLY));
}
setMeasuredDimension(targetView.getMeasuredWidth(), targetView.getMeasuredHeight());
}
}
然后拿到依附控件的父控件,把依附控件从父控件中删除,并把BadgeContainer 添加到父控件中,并把依附控件和自定义View添加到BadgeContainer 中。这样就把我们的自定义View添加在了控件上,但是,现在我们自定义VIew的操作范围,只是依附控件的大小,除非依附控件是全屏的,否者还是没有什么效果,所以我们就要把我们的自定义View再添加到更外层的父控件。
不知道大家认不认识android.R.id.content,这是DecorView下一个FrameLayout的id,我这里直接添加在了id为android.R.id.content的view上。我正在写关于View绘制流程的文章,基本都是源码,所以还得多复查几遍。
private void findActivityRoot(View view) {
if (view.getParent() != null) {
findActivityRoot((View) view.getParent());
} else if (view instanceof FrameLayout && view.getId()==android.R.id.content) {
mActivityRoot = (ViewGroup) view;
}
}
然后在手势滑动的时候判断是添加在控件上还是添加在外层View上
protected void screenFromWindow(boolean screen) {
if (getParent() != null) {
((ViewGroup) getParent()).removeView(this);
}
if (screen) {
//添加到最外层父类
mActivityRoot.addView(this, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
} else {
//添加控件上
bindTarget(mTargetView);
}
}
我在手指落下,可以构成连接状态的时候,将自定义View添加在了外层View上,在回弹动画,和爆炸动画结束后,重新添加在了依附控件上,如果不重新添加在依附控件上,会发现,屏幕所有 的点击事件都被 我们的自定义View拦截进行处理~
这是在处理我们自定义View展示大小是应该注意的,还应该注意,手势处理时使用getRawY用的绝对坐标,之前基础中用的都是相对坐标,计算动圆圆心的时机,重置那些属性以及时机,还有就是很多处理逻辑。。。
最后感谢进化内容参考的项目
BadgeView
没有这个项目,可能我还得好久才能写完进化部分的内容,特别感谢~
写这个项目从最初的画圆,学习基线,写字,到现在的应该说可以在项目中使用(暂时在本机上没有什么明显的bug,性能方面还不太清楚),途中遇到了一位书友~ 一起解决讨论了很多问题,受益很大
这篇文章是边学习边写Demo,然后写的文章,可想而知我还是个很小的菜鸟,如果其中有错误还请指出,我会尽快修改文章,并改正自己的理解,谢谢。
最后附上源码
我朋友和我说,在没看源码之前,根本想不到这种思路,但是看了源码时候,感觉又都会~ 个人理解,其实我们现在都只是一个学习积累的过程,这个效果用到了很多知识点,比如 path,贝塞尔曲线,手势,动画什么的,分成知识点去学习它,然后去组合它们,感觉也是很收益。
你们的点赞,是我们学习分享的最大动力~ 每个人都一样,更何况我们这些菜鸟呢?
网友评论