本來是想寫一篇點贊效果的自定義View實例的,後來好基友因爲項目緊急叫我幫他擼一個條形柱狀圖,大致瞄了一眼原型圖以後便開擼了。最後做出來的效果與原型圖八九不離十,看一下最後實現的效果圖:
一個普通的條形柱狀圖,統計的是12個月份兩種狀態的數值,選中的長條背景顏色會加深,並且顯示當前兩種狀態的數值。看看怎麼實現的吧:
一.準備工作:
1.數據準備:
myChartView = (MyChartView) findViewById(R.id.my_chart_view);
relativeLayout = (RelativeLayout) findViewById(R.id.linearLayout);
Random random = new Random();
while (list.size() < 24) {
int randomInt = random.nextInt(100);
list.add(randomInt);
}
myChartView.setList(list);
這裏生成了24個100以內的隨機整數,所以第二次進入的時候,柱狀圖的高度數值會有所改變。
2.設置屬性,計算寬高,進行必要的初始化:
public MyChartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//獲取我們自定義的樣式屬性
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyChartView, defStyleAttr, 0);
int n = array.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.MyChartView_leftColor:
// 默認顏色設置爲黑色
leftColor = array.getColor(attr, Color.BLACK);
break;
case R.styleable.MyChartView_rightColor:
rightColor = array.getColor(attr, Color.BLACK);
break;
case R.styleable.MyChartView_xyColor:
lineColor = array.getColor(attr, Color.BLACK);
break;
}
}
array.recycle();
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mBound = new Rect();
mChartPaint = new Paint();
mChartPaint.setAntiAlias(true);
mShadowPaint = new Paint();
mShadowPaint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width;
int height;
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = widthSize * 1 / 2;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = heightSize * 1 / 2;
}
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mWidth = getWidth();
mHeight = getHeight();
mStartWidth = getWidth() / 13;
mSize = getWidth() / 39;
mChartWidth = getWidth() / 13 - mSize;
}
相信看過前幾篇博客的小夥伴,對這些代碼以及非常熟悉了,這也是自定義View前必不可少的準備工作。
二.繪製柱狀圖:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(lineColor);
//畫座標軸
canvas.drawLine(0, mHeight - 100, mWidth, mHeight - 100, mPaint);
for (int i = 0; i < 12; i++) {
//畫刻度線
canvas.drawLine(mStartWidth, mHeight - 100, mStartWidth, mHeight - 80, mPaint);
//畫數字
mPaint.setTextSize(30);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.getTextBounds(String.valueOf(i + 1) + "月", 0, String.valueOf(i).length(), mBound);
canvas.drawText(String.valueOf(i + 1) + "月", mStartWidth - mBound.width() * 1 / 2, mHeight - 60 + mBound.height() * 1 / 2, mPaint);
mStartWidth += getWidth() / 13;
}
//畫柱狀圖
for (int i = 0; i < list.size(); i++) {
int size = mHeight / 150;
//偶數
if (i % 2 == 0) {
mChartPaint.setColor(leftColor);
} else {
mChartPaint.setColor(rightColor);
}
mChartPaint.setStyle(Paint.Style.FILL);
//畫陰影
if (i == number * 2 || i == number * 2 + 1) {
mShadowPaint.setColor(Color.LTGRAY);
} else {
mShadowPaint.setColor(Color.WHITE);
}
canvas.drawRect(mChartWidth, 0, mChartWidth + mSize, mHeight - 100, mShadowPaint);
//畫柱狀圖
canvas.drawRect(mChartWidth, mHeight - 100 - list.get(i) * size, mChartWidth + mSize, mHeight - 100, mChartPaint);// 長方形
mChartWidth += (i % 2 == 0) ? (getWidth() / 39) : (getWidth() / 13 - mSize);
}
}
從上到下,循序漸進,這裏分享一個小技巧:自定義View之前先在本子上把大致位置畫出來,座標在紙上算一遍,這樣既不容易出錯,也對自己寫代碼有幫助。實現不是很複雜,代碼註釋也比較清楚,就不詳細分析了。
三.點擊事件的實現:
示例圖中可以看到,點擊柱狀圖的不同位置,會加深點擊區域的背景,並且顯示當前數值,看看怎麼實現的:
@Override
public boolean onTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
int left = 0;
int top = 0;
int right = mWidth / 12;
int bottom = mHeight - 100;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
for (int i = 0; i < 12; i++) {
rect = new Rect(left, top, right, bottom);
left += mWidth / 12;
right += mWidth / 12;
if (rect.contains(x, y)) {
listener.getNumber(i, x, y);
number = i;
invalidate();
}
}
break;
}
return true;
}
public interface getNumberListener {
void getNumber(int number, int x, int y);
}
通過計算得到點擊的區域代表的值,調用invalidate()方法進行重新繪製,加深背景顏色。
示例圖中顯示的數值會以一個小方塊的形式顯示出來,其實是通過接口回調得到點擊區域代表的值,以及點擊的x,y座標。然後怎樣在Activity中使用呢:
myChartView.setListener(new MyChartView.getNumberListener() {
@Override
public void getNumber(int number, int x, int y) {
relativeLayout.removeView(showText);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
params.leftMargin = x - 100;
if (x - 100 < 0) {
params.leftMargin = 0;
} else if (x - 100 > relativeLayout.getWidth() - showText.getWidth()) {
params.leftMargin = relativeLayout.getWidth() - showText.getWidth();
}
params.topMargin = 100;
showText.setLayoutParams(params);
showText.setTextColor(getResources().getColor(R.color.white));
showText.setTextSize(10);
showText.setText("選擇的數字爲:" + list.get(number * 2) + "," + list.get(number * 2 + 1));
showText.setBackground(getResources().getDrawable(R.drawable.flag_01));
relativeLayout.addView(showText);
}
});
實現接口,每次將這個顯示的小方塊動態添加到佈局中去,顯示點擊的數值,加上x座標左右的限制,防止出界。
四.注意事項:
之前幾篇自定義View,有小夥伴和我反應,把應用切換至手機後臺時,再打開應用,發現自定義的View會消失,這是因爲Activity的生命週期影響了自定義View的生命週期。
可參考我之前一篇博客:Android自定義View探索(一)—生命週期
解決的辦法有很多,推薦兩個常用的:
1.在Activity中重寫onResume()方法,設置自定義View需要的初始值
2.在自定義View方法中重寫onWindowVisibilityChanged()方法,設置初始值
這篇博客我採用的是第二種方法:
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (visibility == VISIBLE) {
mSize = getWidth() / 39;
mStartWidth = getWidth() / 13;
mChartWidth = getWidth() / 13 - mSize - 3;
}
}
這樣只要你不退出當前界面,將應用切換至後臺,過一段時間再次進入時,自定義View還是會保持最初的樣子,不會消失的。