当前位置: 移动技术网 > 移动技术>移动开发>Android > Android自定义View模仿虎扑直播界面的打赏按钮功能

Android自定义View模仿虎扑直播界面的打赏按钮功能

2019年07月24日  | 移动技术网移动技术  | 我要评论

前言

作为一个资深篮球爱好者,我经常会用虎扑app看比赛直播,后来注意到文字直播界面右下角加了两个按钮,可以在直播过程中送虎扑币,为自己支持的球队加油。

具体的效果如下图所示:


我个人觉得挺好玩的,所以决定自己实现下这个按钮,废话不多说,先看实现的效果吧:

这个效果看起来和popupwindow差不多,但我是采用自定义view的方式来实现,下面说说过程。

实现过程

首先从虎扑的效果可以看到,它这两个按钮时浮在整个界面之上的,所以它需要和framelayout结合使用,因此我让它的宽度跟随屏幕大小,高度根据dpi固定,它的实际尺寸时这样的:

另外这个view初始化出来我们看到可以分为三块,背景圆、圆内文字、圆上方数字,所以正常状态下,只需要在ondraw方法中画出这三块内容即可。先在初始化方法中将自定义的属性和画笔以及初始化数据准备好:

private void init(context context, attributeset attrs) {
//获取自定义属性
typedarray typedarray = context.obtainstyledattributes(attrs, r.styleable.hoopview);
mthemecolor = typedarray.getcolor(r.styleable.hoopview_theme_color, color.yellow);
mtext = typedarray.getstring(r.styleable.hoopview_text);
mcount = typedarray.getstring(r.styleable.hoopview_count);

mbgpaint = new paint();
mbgpaint.setantialias(true);
mbgpaint.setcolor(mthemecolor);
mbgpaint.setalpha(190);
mbgpaint.setstyle(paint.style.fill);

mpoppaint = new paint();
mpoppaint.setantialias(true);
mpoppaint.setcolor(color.ltgray);
mpoppaint.setalpha(190);
mpoppaint.setstyle(paint.style.fill_and_stroke);

mtextpaint = new textpaint();
mtextpaint.setantialias(true);
mtextpaint.setcolor(mtextcolor);
mtextpaint.settextsize(context.getresources().getdimension(r.dimen.hoop_text_size));

mcounttextpaint = new textpaint();
mcounttextpaint.setantialias(true);
mcounttextpaint.setcolor(mthemecolor);
mcounttextpaint.settextsize(context.getresources().getdimension(r.dimen.hoop_count_text_size));

typedarray.recycle();

mbigradius = context.getresources().getdimension(r.dimen.hoop_big_circle_radius);
msmallradius = context.getresources().getdimension(r.dimen.hoop_small_circle_radius);
margin = (int) context.getresources().getdimension(r.dimen.hoop_margin);
mheight = (int) context.getresources().getdimension(r.dimen.hoop_view_height);
countmargin = (int) context.getresources().getdimension(r.dimen.hoop_count_margin);

mdatas = new string[] {"1", "10", "100"};
// 计算背景框改变的长度,默认是三个按钮
mchangewidth = (int) (2 * msmallradius * 3 + 4 * margin);}

在onmeasure中测出view的宽度后,根据宽度计算出背景圆的圆心坐标和一些相关的数据值。

@override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
int widthsize = measurespec.getsize(widthmeasurespec);
mwidth = getdefaultsize(widthsize, widthmeasurespec);
setmeasureddimension(mwidth, mheight);

// 此时才测出了mwidth值,再计算圆心坐标及相关值
cx = mwidth - mbigradius;
cy = mheight - mbigradius;
// 大圆圆心
circle = new pointf(cx, cy);
// 三个按钮的圆心
circleone = new pointf(cx - mbigradius - msmallradius - margin, cy);
circletwo = new pointf(cx - mbigradius - 3 * msmallradius - 2 * margin, cy);
circlethree = new pointf(cx - mbigradius - 5 * msmallradius - 3 * margin, cy);
// 初始的背景框的边界即为大圆的四个边界点
top = cy - mbigradius;
bottom = cy + mbigradius;
}

因为这里面涉及到点击按钮展开和收缩的过程,所以我定义了如下几种状态,只有在特定的状态下才能进行某些操作。

private int mstate = state_normal;//当前展开收缩的状态
private boolean misrun = false;//是否正在展开或收缩

//正常状态
public static final int state_normal = 0;
//按钮展开
public static final int state_expand = 1;
//按钮收缩
public static final int state_shrink = 2;
//正在展开
public static final int state_expanding = 3;
//正在收缩
public static final int state_shrinking = 4;

接下来就执行ondraw方法了,先看看代码:

@override protected void ondraw(canvas canvas) {
switch (mstate) {
case state_normal:
drawcircle(canvas);
break;
case state_shrink:
case state_shrinking:
drawbackground(canvas);
break;
case state_expand:
case state_expanding:
drawbackground(canvas);
break;
}
drawcircletext(canvas);
drawcounttext(canvas);
}

圆上方的数字和圆内的文字是整个过程中一直存在的,所以我将这两个操作放在switch之外,正常状态下绘制圆和之前两部分文字,点击展开时绘制背景框展开过程和文字,展开状态下再次点击绘制收缩过程和文字,当然在绘制背景框的方法中也需要不断绘制大圆,大圆也是一直存在的。

上面的绘制方法:

/**
 * 画背景大圆
 * @param canvas
 */
private void drawcircle(canvas canvas) {
left = cx - mbigradius;
right = cx + mbigradius;
canvas.drawcircle(cx, cy, mbigradius, mbgpaint);
}


/**
 * 画大圆上面表示金币数的文字
 * @param canvas
 */
private void drawcounttext(canvas canvas) {
canvas.translate(0, -countmargin);
//计算文字的宽度
float textwidth = mcounttextpaint.measuretext(mcount, 0, mcount.length());
canvas.drawtext(mcount, 0, mcount.length(), (2 * mbigradius - textwidth - 35) / 2, 0.2f, mcounttextpaint);
}


/**
 * 画大圆内的文字
 * @param canvas
 */
private void drawcircletext(canvas canvas) {
staticlayout layout = new staticlayout(mtext, mtextpaint, (int) (mbigradius * math.sqrt(2)), layout.alignment.align_center, 1.0f, 0.0f, true);
canvas.translate(mwidth - mbigradius * 1.707f, mheight - mbigradius * 1.707f);
layout.draw(canvas);
canvas.save();
}


/**
 * 画背景框展开和收缩
 * @param canvas
 */
private void drawbackground(canvas canvas) {
left = cx - mbigradius - mchange;
right = cx + mbigradius;
canvas.drawroundrect(left, top, right, bottom, mbigradius, mbigradius, mpoppaint);
if ((mchange > 0) && (mchange <= 2 * msmallradius + margin)) {
// 绘制第一个按钮
canvas.drawcircle(cx - mchange, cy, msmallradius, mbgpaint);
// 绘制第一个按钮内的文字
canvas.drawtext(mdatas[0], cx - (mbigradius - msmallradius) - mchange, cy + 15, mtextpaint);
} else if ((mchange > 2 * msmallradius + margin) && (mchange <= 4 * msmallradius + 2 * margin)) {
// 绘制第一个按钮
canvas.drawcircle(cx - mbigradius - msmallradius - margin, cy, msmallradius, mbgpaint);
// 绘制第一个按钮内的文字
canvas.drawtext(mdatas[0], cx - mbigradius - msmallradius - margin - 20, cy + 15, mtextpaint);

// 绘制第二个按钮
canvas.drawcircle(cx - mchange, cy, msmallradius, mbgpaint);
// 绘制第二个按钮内的文字
canvas.drawtext(mdatas[1], cx - mchange - 20, cy + 15, mtextpaint);
} else if ((mchange > 4 * msmallradius + 2 * margin) && (mchange <= 6 * msmallradius + 3 * margin)) {
// 绘制第一个按钮
canvas.drawcircle(cx - mbigradius - msmallradius - margin, cy, msmallradius, mbgpaint);
// 绘制第一个按钮内的文字
canvas.drawtext(mdatas[0], cx - mbigradius - msmallradius - margin - 16, cy + 15, mtextpaint);

// 绘制第二个按钮
canvas.drawcircle(cx - mbigradius - 3 * msmallradius - 2 * margin, cy, msmallradius, mbgpaint);
// 绘制第二个按钮内的文字
canvas.drawtext(mdatas[1], cx - mbigradius - 3 * msmallradius - 2 * margin - 25, cy + 15, mtextpaint);

// 绘制第三个按钮
canvas.drawcircle(cx - mchange, cy, msmallradius, mbgpaint);
// 绘制第三个按钮内的文字
canvas.drawtext(mdatas[2], cx - mchange - 34, cy + 15, mtextpaint);
} else if (mchange > 6 * msmallradius + 3 * margin) {
// 绘制第一个按钮
canvas.drawcircle(cx - mbigradius - msmallradius - margin, cy, msmallradius, mbgpaint);
// 绘制第一个按钮内的文字
canvas.drawtext(mdatas[0], cx - mbigradius - msmallradius - margin - 16, cy + 15, mtextpaint);

// 绘制第二个按钮
canvas.drawcircle(cx - mbigradius - 3 * msmallradius - 2 * margin, cy, msmallradius, mbgpaint);
// 绘制第二个按钮内的文字
canvas.drawtext(mdatas[1], cx - mbigradius - 3 * msmallradius - 2 * margin - 25, cy + 15, mtextpaint);

// 绘制第三个按钮
canvas.drawcircle(cx - mbigradius - 5 * msmallradius - 3 * margin, cy, msmallradius, mbgpaint);
// 绘制第三个按钮内的文字
canvas.drawtext(mdatas[2], cx - mbigradius - 5 * msmallradius - 3 * margin - 34, cy + 15, mtextpaint);
}
drawcircle(canvas);

}

然后是点击事件的处理,只有触摸点在大圆内时才会触发展开或收缩的操作,点击小圆时提供了一个接口给外部调用。

@override public boolean ontouchevent(motionevent event) {
int action = event.getaction();
switch (action) {
case motionevent.action_down:
//如果点击的时候动画在进行,不处理
if (misrun) return true;
pointf pointf = new pointf(event.getx(), event.gety());
if (ispointincircle(pointf, circle, mbigradius)) { //如果触摸点在大圆内,根据弹出方向弹出或者收缩按钮
if ((mstate == state_shrink || mstate == state_normal) && !misrun) {
//展开
misrun = true;//这是必须先设置true,因为onanimationstart在onanimationupdate之后才调用
showpopmenu();
} else {
//收缩
misrun = true;
hidepopmenu();
}
} else { //触摸点不在大圆内
if (mstate == state_expand) { //如果是展开状态
if (ispointincircle(pointf, circleone, msmallradius)) {
listener.clickbutton(this, integer.parseint(mdatas[0]));
} else if (ispointincircle(pointf, circletwo, msmallradius)) {
listener.clickbutton(this, integer.parseint(mdatas[1]));
} else if (ispointincircle(pointf, circlethree, msmallradius)) {
listener.clickbutton(this, integer.parseint(mdatas[2]));
}
misrun = true;
hidepopmenu();
}
}
break;
}
return super.ontouchevent(event);
}

展开和收缩的动画是改变背景框的宽度属性的动画,并监听这个属性动画,在宽度值改变的过程中去重新绘制整个view。因为一开始我就确定了大圆小圆的半径和小圆与背景框之间的间距,所以初始化时已经计算好了背景框的宽度:

mchangewidth = (int) (2 * msmallradius * 3 + 4 * margin);
/**
 * 弹出背景框
 */
private void showpopmenu() {
if (mstate == state_shrink || mstate == state_normal) {
valueanimator animator = valueanimator.ofint(0, mchangewidth);
animator.addupdatelistener(new valueanimator.animatorupdatelistener() {
@override public void onanimationupdate(valueanimator animation) {
if (misrun) {
mchange = (int) animation.getanimatedvalue();
invalidate();
} else {
animation.cancel();
mstate = state_normal;
}
}
});
animator.addlistener(new animatorlisteneradapter() {
@override public void onanimationstart(animator animation) {
super.onanimationstart(animation);
misrun = true;
mstate = state_expanding;
}


@override public void onanimationcancel(animator animation) {
super.onanimationcancel(animation);
misrun = false;
mstate = state_normal;
}


@override public void onanimationend(animator animation) {
super.onanimationend(animation);
misrun = false;
//动画结束后设置状态为展开
mstate = state_expand;
}
});
animator.setduration(500);
animator.start();
}
}
/**
 * 隐藏弹出框
 */
private void hidepopmenu() {
if (mstate == state_expand) {
valueanimator animator = valueanimator.ofint(mchangewidth, 0);
animator.addupdatelistener(new valueanimator.animatorupdatelistener() {
@override public void onanimationupdate(valueanimator animation) {
if (misrun) {
mchange = (int) animation.getanimatedvalue();
invalidate();
} else {
animation.cancel();
}
}
});
animator.addlistener(new animatorlisteneradapter() {
@override public void onanimationstart(animator animation) {
super.onanimationstart(animation);
misrun = true;
mstate = state_shrinking;
}


@override public void onanimationcancel(animator animation) {
super.onanimationcancel(animation);
misrun = false;
mstate = state_expand;
}


@override public void onanimationend(animator animation) {
super.onanimationend(animation);
misrun = false;
//动画结束后设置状态为收缩
mstate = state_shrink;
}
});
animator.setduration(500);
animator.start();
}
}

这个过程看起来是弹出或收缩,实际上宽度值每改变一点,就将所有的组件重绘一次,只是文字和大圆等内容的尺寸及位置都没有变化,只有背景框的宽度值在变,所以才有这种效果。

在xml中的使用:

<linearlayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignparentbottom="true"
android:layout_marginbottom="20dp"
android:layout_alignparentright="true"
android:orientation="vertical">

<com.xx.hoopcustomview.hoopview
android:id="@+id/hoopview1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginright="10dp"
app:text="支持火箭"
app:count="1358"
app:theme_color="#31a129"/>

<com.xx.hoopcustomview.hoopview
android:id="@+id/hoopview2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginright="10dp"
app:text="热火无敌"
app:count="251"
app:theme_color="#f49c11"/>
</linearlayout>

activity中使用:

hoopview1 = (hoopview) findviewbyid(r.id.hoopview1);
hoopview1.setonclickbuttonlistener(new hoopview.onclickbuttonlistener() {
@override public void clickbutton(view view, int num) {
toast.maketext(mainactivity.this, "hoopview1增加了" + num, toast.length_short).show();
}
});

大致实现过程就是这样,与原始效果还是有点区别,我这个还有很多瑕疵,比如文字的位置居中问题,弹出或收缩时,小圆内的文字的旋转动画我没有实现。

总结

以上就是这篇文章的全部内容了,希望本文的内容对各位android开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对移动技术网的支持。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网