最近一直在看各路大神的自定义控件,自己受益非浅,可是一直也没有自己动手写一个,这几天有一个项目中要求有如下图这样一个功能:
两个动态值,根据其占比,在这个横柱上显示出来,中间的隔线要有一定的斜角。
在网上找了半天,没有发现什么现成的。突然想到,学了这么长时间自定义控件了,感觉这个也不是很难,就自己做一个试试呗。先理清一下思路。
首先,这个不能采用画矩形图,因为中间无法成斜线,可以采用Path的方法。用路径 的方法画来。里面的百分比,直接进行百分比计算就完了,没什么特别的地方,显示百分比会有两种情况,一种是图中的样子,两部分全有值,百分比显示在左右两边, 另一种是其中一个值为0,则另一个会为100%,这样让百分比显示在中间。这个加一个判断也就OK了。这样简单分析一下,感觉很简单没什么东西了。下面给出代码:
attrs.xml这里定义一些属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
//自定义属性名,定义公共属性
<attr name="iNum" format="float" />
<attr name="iColor" format="color" />
<attr name="oNum" format="float" />
<attr name="oColor" format="color" />
<attr name="Inclination" format="integer"/>
<attr name="iTextColor" format="color" />
<attr name="TextSize" format="dimension" />
<attr name="oTextColor" format="color" />
//自定义控件的主题样式
<declare-styleable name="MyView">
<attr name="iNum" />
<attr name="iColor" />
<attr name="oNum" />
<attr name="oColor" />
<attr name="Inclination" />
<attr name="iTextColor" />
<attr name="TextSize" />
<attr name="oTextColor" />
</declare-styleable>
</resources>
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import java.text.DecimalFormat;
public class MyView extends View {
private float iNum = 50; //进(左)的数量
private int iColor = Color.RED; //进的颜色
private float oNum = 50; //出(右)的数量
private int oColor = Color.GREEN; //出的颜色
private int mInclination = 40; //两柱中间的倾斜度
private int iTextColor = Color.WHITE; //进的百分比数字颜色
private int oTextColor = Color.WHITE; //出的百分比数字颜色
private int TextSize = 30; //百分比字体大小
private float iPre;
private float oPre;
private String txtiPre; //显示进的百分比
private String txtoPre; //显示出的百分比
private Paint mPaint;
private Rect mBound; //包含文字的框
public MyView(Context context) {
super(context);
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(5);
mBound = new Rect();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
TypedArray arry = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyView,defStyleAttr,0);
int n = arry.getIndexCount();
for(int i=0;i<n;i++)
{
int attr = arry.getIndex(i);
switch (attr)
{
case R.styleable.MyView_iNum:
iNum = arry.getFloat(attr, 50);
break;
case R.styleable.MyView_iColor:
iColor = arry.getColor(attr, Color.RED);
break;
case R.styleable.MyView_oNum:
oNum = arry.getFloat(attr, 50);
break;
case R.styleable.MyView_oColor:
oColor = arry.getColor(attr,Color.GREEN);
break;
case R.styleable.MyView_Inclination:
mInclination = arry.getInt(attr,40);
break;
case R.styleable.MyView_iTextColor:
iTextColor = arry.getColor(attr,Color.WHITE);
break;
case R.styleable.MyView_oTextColor:
oTextColor = arry.getColor(attr,Color.WHITE);
break;
case R.styleable.MyView_TextSize:
TextSize = arry.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;
}
}
arry.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = getPaddingLeft() + getWidth() + getPaddingRight();
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = getPaddingTop() + getHeight() + getPaddingBottom();
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
iPre = (iNum / (iNum + oNum)) * getWidth();
oPre = (oNum / (iNum + oNum)) * getWidth();
//Log.e("mPre", "iPre:" + iPre + " oPre:" + oPre + "width" + getWidth());
//如果进值或出值有一个为0,则另一个就会占满整个进度条,这时就不需要倾斜角度了
if (iNum == 0 || oPre == 0) {
mInclination = 0;
}
Path iPath = new Path();
iPath.moveTo(0, 20);
iPath.quadTo(0, 0, 20, 0);
iPath.lineTo(iPre + mInclination, 0);
iPath.lineTo(iPre, getHeight());
iPath.lineTo(20, getHeight());
iPath.close();
mPaint.setColor(iColor);
canvas.drawPath(iPath, mPaint);
Path oPath = new Path();
oPath.moveTo(iPre + mInclination, 0);
oPath.lineTo(getWidth()-20, 0);
oPath.lineTo(getWidth(), getHeight());
oPath.quadTo(getWidth(), getHeight(), getWidth() - 20, getHeight());
oPath.lineTo(iPre - mInclination, getHeight());
oPath.close();
mPaint.setColor(oColor);
canvas.drawPath(oPath, mPaint);
txtiPre = getProValText(iNum / (iNum + oNum) * 100);
txtoPre = getProValText(oNum / (iNum + oNum) * 100);
mPaint.setColor(iTextColor);
mPaint.setTextSize(TextSize);
mPaint.getTextBounds(txtiPre, 0, txtiPre.length(), mBound);
//判断一下,如果进值为0则不显示,如果进值不为空而出值为0,则进值的数值显示居中显示
if (iNum != 0 && oNum != 0) {
canvas.drawText(txtiPre, 20, getHeight() / 2 + mBound.height() / 2, mPaint);
} else if (iNum != 0 && oNum == 0) {
canvas.drawText(txtiPre, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
}
mPaint.setColor(oTextColor);
mPaint.getTextBounds(txtoPre, 0, txtoPre.length(), mBound);
if (oNum != 0 && iNum != 0) {
canvas.drawText(txtoPre, getWidth() - 20 - mBound.width(), getHeight() / 2 + mBound.height() / 2, mPaint);
} else if (oNum != 0 && iNum == 0) {
canvas.drawText(txtoPre, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
}
}
/**
* 格式化显示的百分比
*
* @param proValue
* @return
*/
private String getProValText(float proValue) {
DecimalFormat format = new DecimalFormat("#0.0");
return format.format(proValue) + "%";
}
/**
* 动态设置进值
*
* @param iNum
*/
public void setINum(float iNum) {
this.iNum = iNum;
postInvalidate();
}
/**
* 动态设置出值
*
* @param oNum
*/
public void setONum(float oNum) {
this.oNum = oNum;
postInvalidate();
}
}
调用的时候:
布局文件activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:context=".MainActivity">
<com.example.test.MyView
android:layout_marginTop="10dp"
android:id="@+id/myView"
android:layout_width="0dp"
android:layout_height="18dp"
android:layout_marginStart="10dp"
android:layout_weight="1"
app:TextSize="14sp" />
<Button
android:id="@+id/btn_Add"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="变化开始"/>
MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_Add;
private mPre myPre;
private int iNum = 50;
private int oNum = 50;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initControls();
}
/**
* 初始化控件
*/
private void initControls() {
myPre = (mPre)findViewById(R.id.myPre);
btn_Add = (Button)findViewById(R.id.btn_Add);
btn_Add.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId())
{
case R.id.btn_Add:
iNum = iNum + 5;
myPre.setINum(iNum);
//myPre.setONum(0);
break;
}
}
}