先上个GIF图,看一下点赞效果,这个效果也是比较常见的。下面的内容就是分析下这个点赞自定义View的实现和自己在实现过程中学习巩固的知识点。

首先我把整个点赞分成两个部分,一个是手指点亮的ThumbUpView,另一个是数字变化的ThumbUpNumberView。这样做的好处是点赞动画,和数字变化分开,到时候需要修改点赞动画时会方便一些。接下来一个个分析。
ThumbUpView
- 有下面两张图片组成,并且两张图片有重叠部分,所以决定让ThumbUpView继承FrameLayout。两个子View组合成点赞图片。


- 动画效果的实现,点亮效果是:图1和图2都是从无到有,仔细看甚至是先变大到原图120%后变小到原图100%的过程。取消点亮效果:图2消失,灰色手指和图1点亮一样出现。
思路分析完,看具体代码的实现
点赞图片组合的实现
//创建两个ImageView,添加到ThumbUpView中,在onLayout中设置两个ImageView的位置
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//在super.onLayout下面更改子view布局才有效果
int count = getChildCount();
for(int i = 0 ; i < count ; i++){
View view = getChildAt(i);
mChildViewLeft = view.getLeft();
mChildViewTop = view.getTop();
mChildViewRight = view.getRight();
mChildViewBottom = view.getBottom();
if(i == count - 1){//手指图片,整体往下
view.layout(mChildViewLeft,mChildViewTop + 50,mChildViewRight,mChildViewBottom + 50);
}else{
view.layout(mChildViewLeft,mChildViewTop,mChildViewRight,mChildViewBottom);
}
}
}
动画效果的实现
//将点击事件等交给GestureDetector处理 flag作为是否点赞的标记
mGestureDetector = new GestureDetector(context, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return true;//返回true,才能进入onSingleTapUp事件中
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
//单击
Log.i(TAG, "onSingleTapUp: ");
if(flag){//已经点赞,取消点赞
mShiningIv.setVisibility(INVISIBLE);
mLikeIv.setImageBitmap(mLikeUnselectedBp);
//时间0%时,变化值为0f
Keyframe keyframe1 = Keyframe.ofFloat(0,0f);
//时间80%时,变化值为1.2f
Keyframe keyframe2 = Keyframe.ofFloat(0.8f,1.2f);
//时间100%时,变化值为1.0f
Keyframe keyframe3 = Keyframe.ofFloat(1,1.0f);
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofKeyframe("scaleX",keyframe1,keyframe2,keyframe3);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofKeyframe("scaleY",keyframe1,keyframe2,keyframe3);
ObjectAnimator.ofPropertyValuesHolder(mLikeIv,pvh1,pvh2).setDuration(200).start();
mThumbNumberView.cancelThumbUp();
}else{//未点赞,进行点赞
mShiningIv.setVisibility(VISIBLE);
mLikeIv.setImageBitmap(mLikeSelectedBp);
//时间0%时,变化值为0f
Keyframe keyframe1 = Keyframe.ofFloat(0,0f);
//时间80%时,变化值为1.2f
Keyframe keyframe2 = Keyframe.ofFloat(0.8f,1.2f);
//时间100%时,变化值为1.0f
Keyframe keyframe3 = Keyframe.ofFloat(1,1.0f);
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofKeyframe("scaleX",keyframe1,keyframe2,keyframe3);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofKeyframe("scaleY",keyframe1,keyframe2,keyframe3);
ObjectAnimator.ofPropertyValuesHolder(mLikeIv,pvh1,pvh2).setDuration(200).start();
ObjectAnimator.ofPropertyValuesHolder(mShiningIv,pvh1,pvh2).setDuration(200).start();
mThumbNumberView.thumbUp();
}
flag = !flag;
return false;
}
........
});
ThumbUpNumberView

- 数值的变化只是改变变动部分。因此考虑把数值部分分为3类(String num[3]),不变部分num[0](在上图中是百位和十位上的99 ),原来变化部分num[1](原本个位上的8),后来的变化部分num[2](最后个位上的9)
- 数值的变化是一个滚动的动画效果,点赞的时候数值上翻,取消点赞的时候数值下翻。实现思路:设置OldoffsetY,offsetY来控制num[1],num[2]的一点时间内的变化,num[0]保持不动。
- 数值的变化中,num[1]总是从看的见到看不见,num[2]总是从看不见到看的见。这只要控制画笔颜色的ARGB值
思路分析完,看具体代码的实现
计算出数值的三个部分
/**
* 计算num不变及变化部分
* @param newCount
*/
private void calculateNum(int newCount) {
mNum[0]=mNum[1]=mNum[2]="";
String oldCountStr = String.valueOf(mCount);
String newCountStr = String.valueOf(newCount);
if(oldCountStr.length() != newCountStr.length()){//位数发生变化
mNum[1] = oldCountStr;
mNum[2] = newCountStr;
}else{
int length = oldCountStr.length();
for(int i = 0 ; i < length ; i++){
if(oldCountStr.charAt(i) == newCountStr.charAt(i)){//相同部分
mNum[0] += oldCountStr.charAt(i);
}else{//不同部分
mNum[1] += oldCountStr.charAt(i);
mNum[2] += newCountStr.charAt(i);
}
}
}
}
滚动效果实现 mOffsetYFraction是自定义一个属性,用来获取一段时间中的变化率,需要实现get,set方法。在set方法中根据实际情况通过mOffsetYFraction计算出mOffsetY和mOldOffsetY来改变num[1],num[2]的位置。
//文本颜色
private static final int TEXT_DEFAULT_COLOR = Color.parseColor("#cccccc");
private static final int TEXT_DEFAULT_END_COLOR = Color.parseColor("#00cccccc");
//变化率
private float mOffsetYFraction = 0.0f;
public float getMOffsetYFraction() {
return mOffsetYFraction;
}
//setMOffsetYFraction中M必须大写,否则不会调用
public void setMOffsetYFraction(float mOffsetYFraction) {
this.mOffsetYFraction = mOffsetYFraction;
if(mOffsetYFraction < 0){//数字上翻[0,-1]
mOffsetY = mOffsetYFraction * mTextHeight;
mOldOffsetY = mOffsetYFraction * mTextHeight;
}else{//数字下翻[0,1]
mOffsetY = -2*mTextHeight + mOffsetYFraction*mTextHeight;
mOldOffsetY = mOffsetYFraction * mTextHeight;
}
//在变化区间内会不断调用
postInvalidate();
}
/**
* 点赞动画
*/
private void ThumbUpAnimator() {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this,"mOffsetYFraction",0f,-1.0f);
objectAnimator.setDuration(200);
objectAnimator.start();
}
/**
* 取消点赞动画
*/
private void ThumbDownAnimator(){
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this,"mOffsetYFraction",0f,1.0f);
objectAnimator.setDuration(200);
objectAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setColor(TEXT_DEFAULT_COLOR);
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
//通过FontMetrics获得文本宽度,单个字符宽度,文本高度
float textWidth = paint.measureText(String.valueOf(mCount));
float charWidth = textWidth / String.valueOf(mCount).length();
mTextHeight = fontMetrics.bottom - fontMetrics.top;
float fraction = Math.abs(mOffsetYFraction);
//画不变部分
canvas.drawText(mNum[0], 0 , mTextHeight, paint);
//画变化的原来部分
paint.setColor((Integer) evaluate(fraction, TEXT_DEFAULT_COLOR, TEXT_DEFAULT_END_COLOR));
canvas.drawText(mNum[1], charWidth*mNum[0].length(), mTextHeight + mOldOffsetY, paint);
//画变化的新的部分,将mNum[2]放在mNum[1]正下面
paint.setColor((Integer) evaluate(fraction, TEXT_DEFAULT_END_COLOR, TEXT_DEFAULT_COLOR));
canvas.drawText(mNum[2], charWidth*mNum[0].length(), mTextHeight*2 + mOffsetY, paint);
}
字体颜色变化,通过evaluate方法
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
float startA = ((startInt >> 24) & 0xff) / 255.0f;
float startR = ((startInt >> 16) & 0xff) / 255.0f;
float startG = ((startInt >> 8) & 0xff) / 255.0f;
float startB = (startInt & 0xff) / 255.0f;
int endInt = (Integer) endValue;
float endA = ((endInt >> 24) & 0xff) / 255.0f;
float endR = ((endInt >> 16) & 0xff) / 255.0f;
float endG = ((endInt >> 8) & 0xff) / 255.0f;
float endB = (endInt & 0xff) / 255.0f;
// convert from sRGB to linear
startR = (float) Math.pow(startR, 2.2);
startG = (float) Math.pow(startG, 2.2);
startB = (float) Math.pow(startB, 2.2);
endR = (float) Math.pow(endR, 2.2);
endG = (float) Math.pow(endG, 2.2);
endB = (float) Math.pow(endB, 2.2);
// compute the interpolated color in linear space
float a = startA + fraction * (endA - startA);
float r = startR + fraction * (endR - startR);
float g = startG + fraction * (endG - startG);
float b = startB + fraction * (endB - startB);
// convert back to sRGB in the [0..255] range
a = a * 255.0f;
r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;
return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
}
网友评论