前段时间从朋友那听说一个需求,实现一个商品展示并且添加商品的描述,闲暇时间试着自己实现了一下。
下面看一下效果图:
lufei.gif
当然啦,对于萌新的我只能实现基本功能,与君共勉,还有很多可以优化的地方。下面贴出实现代码,希望可以帮到有需要的兄弟。
自定义属性部分:
<!--商品图片描述-->
<declare-styleable name="ImgDescribeView">
<attr name="imgDescribeBackground" format="reference" /> <!--图片-->
<attr name="imgContentText" format="string" /> <!--描述内容-->
<attr name="imgContentTextColor" format="color" /> <!--描述内容颜色-->
<attr name="imgContentTextSize" format="dimension" /> <!--描述内容字体大小-->
<attr name="imgLineColor" format="color" /> <!--线颜色-->
<attr name="imgLineWidth" format="dimension" /> <!--线宽-->
<attr name="imgLineAngle" format="float" /> <!--线角度-->
<!--文字方向-->
<attr name="imgTextForest">
<enum name="leftToRight" value="0" />
<enum name="rightToLeft" value="1" />
</attr>
</declare-styleable>
XML布局引用:
<com.fivefloor.bo.myview.widgets.ImgDescribeAnimatorView
android:id="@+id/img_line"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_200"
app:imgContentText="现在测试"
app:imgContentTextColor="@color/colorAccent"
app:imgContentTextSize="16sp"
app:imgDescribeBackground="@mipmap/ic_launcher_round"
app:imgLineAngle="205"
app:imgLineColor="@color/colorRed"
app:imgLineWidth="1dp"
app:imgTextForest="rightToLeft"/>
自定义view:
package com.fivefloor.bo.myview.widgets;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import com.fivefloor.bo.myview.R;
/**
* Author:bobo
* <p>
* Create Time:2018/8/15 10:13
* <p>
*
* <p>
* Describe:动态画线,给图片添加文字说明
*/
public class ImgDescribeAnimatorView extends View {
/**
* 小圆心点坐标,整个控件图形以圆心点的圆心坐标为基准绘画
*/
private float x_CirclePoint_R, y_CirclePoint_R;
/**
* 小圆心点半径
*/
private float CirclePoint_R = 10;
private Path pathLine;//画线路径
private Paint paintLine;//画笔
private int paintLineColor;//画线颜色
private float angle_a = (float) (Math.PI / 180);//弧度转角度
private float angle = (float) (angle_a * 205);//角度
private Paint paintTxt;
private String textContent;
/**
* 图片地址
*/
private int imgId;
/**
* 默认字体大小sp
*/
private float textSize;
/**
* 默认字体颜色
*/
private int textColor = Color.BLACK;
/**
* 线1随动画动态坐标
*/
private float x_Animator1, y_Animator1;
/**
* 线2随动画动态坐标
*/
private float x_Animator2, y_Animator2;
/**
* 位图
*/
private Bitmap bitmap;
/**
* 文字边框,用于得到内容描述的大小属性等
*/
private Rect rect;
/**
* 内容描述渐变显示度
*/
private float txtProgerss;
/**
* 设置开始写字方向,默认从左往右
*/
private Paint.Align direction = Paint.Align.LEFT;
/**
* 线宽
*/
private float lineWidth = 1;
public ImgDescribeAnimatorView(Context context) {
this(context, null);
}
public ImgDescribeAnimatorView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ImgDescribeAnimatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, 0, 0);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ImgDescribeAnimatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initAttrs(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//因为是圆形图片,所以应该让宽高保持一致
int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
//图片宽占控件的1/3
mWidth = size / 3;
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
x_CirclePoint_R = w / 2;//小圆心点坐标
y_CirclePoint_R = h / 3;
initPaint();
}
/**
* 初始化加载设置属性
*/
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImgDescribeView);
//获取商品图片
int src = typedArray.getResourceId(R.styleable.ImgDescribeView_imgDescribeBackground, R.drawable.icon);
this.imgId = src;
//描述内容
String text = typedArray.getString(R.styleable.ImgDescribeView_imgContentText);
if (text != null)
this.textContent = text;
else
this.textContent = "通缉路飞";
//描述内容颜色
int textcolor = typedArray.getColor(R.styleable.ImgDescribeView_imgContentTextColor, Color.BLACK);
this.textColor = textcolor;
//描述内容字体大小
float textsize = typedArray.getDimensionPixelSize(R.styleable.ImgDescribeView_imgContentTextSize, 12);
setTextContentSize(textsize);
//线颜色
int linecolor = typedArray.getColor(R.styleable.ImgDescribeView_imgLineColor, Color.BLACK);
this.paintLineColor = linecolor;
//线宽
float lineW = typedArray.getDimension(R.styleable.ImgDescribeView_imgLineWidth, 1);
setLineWidth(lineW);
//线角度
float lineangle = typedArray.getFloat(R.styleable.ImgDescribeView_imgLineAngle, 205);
this.angle = angle_a * lineangle;
//内容描述动画开始方向
int textForest = typedArray.getInt(R.styleable.ImgDescribeView_imgTextForest, 0);
if (textForest == 0) {
setTextCanvasLeftToRight();
} else {
setTextCanvasRightToLeft();
}
typedArray.recycle();
}
/**
* 初始化画笔
*/
private void initPaint() {
paintLine = new Paint();
paintLine.setAntiAlias(true);
paintLine.setStrokeCap(Paint.Cap.ROUND);//线帽
paintLine.setColor(paintLineColor);
paintLine.setStrokeWidth(lineWidth);//线宽
paintLine.setTextAlign(Paint.Align.CENTER);
paintLine.setStyle(Paint.Style.FILL_AND_STROKE);//描边+填充
pathLine = new Path();
// 获取图片方法1
// try {
// InputStream is = getContext().getAssets().open("ic_launcher_round.png");
// bitmap = BitmapFactory.decodeStream(is);
// is.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// // 获取图片方法2
bitmap = BitmapFactory.decodeResource(getContext().getResources(), imgId);
//文本画笔设置及属性
rect = new Rect();
paintTxt = new Paint();
paintTxt.setAntiAlias(true);
paintTxt.setStyle(Paint.Style.FILL);
paintTxt.setTextSize(textSize);
paintTxt.getTextBounds(textContent, 0, textContent.length(), rect);//获得文本框
initAnimator();
}
private void initAnimator() {
ValueAnimator animatorLine1 = ValueAnimator.ofFloat(0, 100).setDuration(1000);//线1
ValueAnimator animatorLine2 = ValueAnimator.ofFloat(0, 50).setDuration(1000);//线2
ValueAnimator animatorTxt = ValueAnimator.ofFloat(0, 1).setDuration(2000);//内容text
animatorTxt.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//字体
txtProgerss = (float) animation.getAnimatedValue();
invalidate();
}
});
animatorLine1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//线1
x_Animator1 = (float) animation.getAnimatedValue();
y_Animator1 = (float) ((float) animation.getAnimatedValue() * Math.tan(angle));
invalidate();//刷新ui
}
});
animatorLine2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//线2
x_Animator2 = (float) animation.getAnimatedValue();
invalidate();//刷新ui
}
});
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(animatorLine1).before(animatorLine2).before(animatorTxt);
animatorSet.start();
}
@SuppressLint("WrongConstant")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawRectImg(canvas);
paintLine.setStyle(Paint.Style.FILL_AND_STROKE);//描边+填充
canvas.drawCircle(x_CirclePoint_R, y_CirclePoint_R, CirclePoint_R, paintLine);//画小圆心点
//------画线1-------
paintLine.setStyle(Paint.Style.STROKE);//描边
pathLine.moveTo(x_CirclePoint_R, y_CirclePoint_R);
pathLine.lineTo(x_CirclePoint_R - x_Animator1, y_CirclePoint_R - y_Animator1);
if (x_Animator2 != 0) {
//-------画线2-------
pathLine.rLineTo(-x_Animator2, 0);//在原有的基础上延伸
}
canvas.drawPath(pathLine, paintLine);
if (txtProgerss != 0) {
//内容描述
drawTextContent(canvas);//渐变的内容描述
}
paintLine.descent();
paintTxt.descent();
}
/**
* 画方形图
* @param canvas
*/
private void drawRectImg(Canvas canvas) {
// 指定图片绘制区域,这里设置与图片参数大小一致,绘制完整图片
Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
// 指定图片在屏幕上显示的区域,大了会缩小,小了会放大
//开始点位置
int sX = (int) (x_CirclePoint_R + CirclePoint_R + 10);//+10像素为了美观
int sY = (int) (y_CirclePoint_R - 2 * CirclePoint_R);
//缩放比例(宽高缩放比例相同)
float scale=mWidth/(float)bitmap.getWidth();
//结束点位置
int sX2 = (int) (x_CirclePoint_R + CirclePoint_R + mWidth + 10);
int sY2 = (int) (y_CirclePoint_R - 2 * CirclePoint_R + bitmap.getHeight()*scale);
Rect dst = new Rect(sX, sY, sX2, sY2);
// 绘制图片
canvas.drawBitmap(bitmap, src, dst, null);
}
private int mWidth;//图像宽
@SuppressLint("WrongConstant")
private void drawTextContent(Canvas canvas) {
int startX;//裁剪开始位置,结合动画实时刷新实现视觉上的文字渐变效果
int endX;//裁剪结束位置
int textLeft = (int) (x_CirclePoint_R - x_Animator1 - x_Animator2 - rect.width()); //文本在控件中的起始x位置
int textRight = (int) (x_CirclePoint_R - x_Animator1 - x_Animator2); // 文本在控件中的结束x位置
float textBottom = y_CirclePoint_R - y_Animator1 + rect.height() / 2; //文本在控件中的结束y位置
if (txtProgerss < 0) {
txtProgerss = 0;
}
if (txtProgerss > 1) {
txtProgerss = 1;
}
int changedWidth = (int) ((rect.width() + rect.left) * txtProgerss);
if (direction == Paint.Align.LEFT) {
//从左往右显示,裁剪区
startX = textLeft;
endX = textLeft + changedWidth;
} else {
//从右往左显示,裁剪区
startX = textRight - changedWidth;
endX = textRight;
}
//画渐变部分的文字
canvas.save(Canvas.CLIP_SAVE_FLAG);
paintTxt.setColor(textColor);
canvas.clipRect(startX, 0, endX, getMeasuredHeight());
canvas.drawText(textContent, textLeft, textBottom, paintTxt);
canvas.restore();
}
/**
* 设置描述内容从左往右
*/
public void setTextCanvasLeftToRight() {
direction = Paint.Align.LEFT;
invalidate();
}
/**
* 设置描述内容从右往左
*/
public void setTextCanvasRightToLeft() {
direction = Paint.Align.RIGHT;
invalidate();
}
/**
* 设置内容描述字体大小sp
*/
public void setTextContentSize(float size) {
textSize = spToPx(getContext(), size);
}
/**
* 将sp换成px,保证尺寸不变
*
* @param context
* @param spValue
* @return
*/
private float spToPx(Context context, float spValue) {
float scaleDensity = context.getResources().getDisplayMetrics().scaledDensity;
return (float) (spValue * scaleDensity + 0.5f);
}
/**
* @param lineW 线宽度
* @return
*/
public void setLineWidth(float lineW) {
lineWidth = lineW;
}
}