还有十几天就毕业啦,给想从事Android的同学说点心里话,如果你刚开始学Android,我建议你赶紧换方向,实在是太难找工作了(巨坑),安卓从2010年开始火到现在都7年了,技术优秀的大牛和普通技术的人都已经一大批啦,我好几个同学都转Java了(基础水平),我的Android技术实话都是参加工作实习后快速提高的(大学里当然是打游戏泡妞的啦!),你如果是要往Android那你就要会的比别人多,不要老用库写软件(没有技术含量),出来实习面试千万要谦虚,我当初就是觉得自己很牛逼跟面试官说相比。忙完了论文(向公司请了1个月的假,回去就要转正辣),自己写了撸了个图表demo,主要是知道回公司要做的APP有很多数据显示。进入正文:
做自定义view,需求是
1. 动态添加横向和竖向标题
2. 动态向单元格写入内容
3. 数据太多,需要滚动显示
在写自定义列表CellChatView前,我们先来认识onMeasure()的使用,我的理解:该方法是子view通知上一层父容器自己设置的宽高大小,需要再调用setMeasuredDimension(int w,int h)设置自定义view的宽高大小,该方法会触发父控件要求子控件的宽高遵循MeasureSpec的3种模式,1).at_mostly,随便设置。2).exactly,确定的大小如50dp.3). 基本不用不讲。
开始画图表,继承容器的选4大layout,搞控件的继承View就可以。
思路:画图表,横向我们可以每一行循环地画矩形,竖向我们画若干条竖线就可以。备注:控件最麻烦的就是宽高的算法,大家可以多看大神们的思路方法。
package cn.jason.com.chatdemo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by jason on 17/5/21.
*/
public class CellChatView extends View {
public static final String TAG = "CellChat>>";
private Context context;
public CellChatView(Context context) {
super(context, null);
}
public CellChatView(Context context, AttributeSet set) {
super(context, set);
this.context = context;
init();
}
int cellHeight;
float fontSize = 18f;
//画笔 文字
private Paint textPaint;
//画笔 文字
private Paint dataPaint;
//画笔 表格
private Paint chatPaint;
//横向 标题
public List<String> xlist;
//纵向 标题
public List<String> ylist;
//横向 小标题
public List<String> fxlist;
//数据
public Map<Integer, List> dataMap = new HashMap<>();
public boolean isAddData = false;
private void init() {
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dataPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
chatPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(Color.BLACK);
dataPaint.setColor(Color.YELLOW);
chatPaint.setColor(Color.BLACK);
chatPaint.setStrokeWidth(2);
textPaint.setTextSize(22f); // 文字大小
textPaint.setTypeface(Typeface.SANS_SERIF); // 字体类型
textPaint.setTextAlign(Paint.Align.CENTER); // 居中
textPaint.setAntiAlias(true); // 是否抗锯齿
xlist = new ArrayList<>();
ylist = new ArrayList<>();
fxlist = new ArrayList<>();
//处理view的高度
this.post(new Runnable() {
@Override
public void run() {
int viewHeight = getHeight() / 2; //view从屏幕一半开始
cellHeight = dpToPx(context, 30);
int needHeight = cellHeight * (ylist.size()) * 2;
Log.v(TAG, "viewHeight=" + viewHeight);
Log.v(TAG, "needHeight=" + needHeight);
if (viewHeight < needHeight) { //当数据很多时,延长view
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
params.height = needHeight + viewHeight+cellHeight; //再加一个cellHeight是因为华为手机 的虚拟键会隐藏,影响高度
setLayoutParams(params);
Log.v(TAG, "viewHeight2=" + getHeight());
} else if (viewHeight > needHeight) {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
params.height = viewHeight + needHeight;
setLayoutParams(params);
Log.v(TAG, "cellHeight=" + cellHeight);
}
}
});
// Log.v(TAG, xlist.size() + " " + ylist.size());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 1.获取屏幕的宽高
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
int cwidth = dm.widthPixels; //单位像素px
int cheight = dm.heightPixels; //注意 /2
Log.v(TAG, "cwidth=" + cwidth + " cheight=" + cheight);
//最小行的高度 px(不用px的在不同分辨率的手机界面会被遮挡的)
// cellHeight = cheight / ((ylist.size() + 1) * 2);
//最大单元格的长度
int cellWidth = cwidth / xlist.size();
//绘制背景 px
for (int i = 0; i < ylist.size(); i++) {
addCell(canvas, cellWidth, cellHeight, i, cheight / 2);
}
//画表格线 竖线
for (int i = 0; i < xlist.size(); i++) {
if (i > 0) {
canvas.drawLine(cellWidth * i, cheight / 2, cellWidth * i, cheight / 2 + cellHeight * (2 * ylist.size() - 1), chatPaint);
}
}
for (int i = 1; i < xlist.size(); i++) {
if (i < xlist.size() - 1) {
canvas.drawLine(i * cellWidth + cellWidth / 2, cheight / 2 + cellHeight, i * cellWidth + cellWidth / 2, cheight / 2 + cellHeight * (2 * ylist.size() - 1), chatPaint);
}
}
//画标题横线
canvas.drawLine(0, cheight / 2, cwidth, cheight / 2, chatPaint);
canvas.drawLine(cellWidth, cheight / 2 + 2 * cellHeight, getWidth() - cellWidth, cheight / 2 + 2 * cellHeight, chatPaint);
//画底部横线
canvas.drawLine(0, cheight / 2 + cellHeight * (2 * ylist.size() - 1), cwidth, cheight / 2 + cellHeight * (2 * ylist.size() - 1), chatPaint);
//添加数据
if (isAddData) {
addServiceData(canvas, cellWidth, cellHeight, cheight / 2);
isAddData = false;
dataMap.clear();
}
}
/**
* @param canvas
* @param cellWidth 最大单元格的宽度
* @param cellHeight 最小单元格的高度
* @param index 第几行
* @param centerY 起始纵向坐标 ,这里需求是屏幕一半的位置
*/
private void addCell(Canvas canvas, int cellWidth, int cellHeight, int index, int centerY) {
Paint paintBK = new Paint(Paint.ANTI_ALIAS_FLAG);
if (index == 0) {
paintBK.setColor(Color.GREEN);
} else if (index % 2 == 1) {
paintBK.setColor(Color.WHITE);
} else if (index % 2 == 0) {
paintBK.setColor(0xfff2f2f2);
}
for (int i = 0; i < xlist.size(); i++) {
Rect rect;
if (index == 0) {
rect = new Rect(cellWidth * i, centerY, cellWidth * i + cellWidth, centerY + cellHeight);
canvas.drawRect(rect, paintBK);
addDataText(canvas, xlist.get(i), fontSize-(xlist.size()-4)*2, rect);
} else {
rect = new Rect(cellWidth * i,
centerY + cellHeight + (index - 1) * 2 * cellHeight,
cellWidth * i + cellWidth,
centerY + cellHeight + (index - 1) * 2 * cellHeight + 2 * cellHeight);
canvas.drawRect(rect, paintBK);
if (i == 0) {
addDataText(canvas, ylist.get(index), fontSize-(xlist.size()-4)*2, rect);
}
if (index == 1) {
if (i > 0 && i < xlist.size() - 1) { //第一行 中间的小标题
if (fxlist.size() - 1 < 2 * (i - 1)) { //处理小标题没有了
return;
}
Rect titleRect1 = new Rect(cellWidth * i, centerY + cellHeight, cellWidth * i + cellWidth / 2, centerY + cellHeight * 2);
Rect titleRect2 = new Rect(cellWidth * i + cellWidth / 2, centerY + cellHeight, cellWidth * (i + 1), centerY + cellHeight * 2);
addDataText(canvas, fxlist.get((i - 1) * 2), fontSize -6-(xlist.size()-4)*3, titleRect1);
addDataText(canvas, fxlist.get((i - 1) * 2 + 1), fontSize - 6-(xlist.size()-4)*3, titleRect2);
}
}
}
// canvas.drawRect(rect, paintBK); //注意画 背景 画 字体 的执行顺序(覆盖)
}
}
private void addServiceData(Canvas canvas, int cellWidth, int cellHeight, int centerY) {
String text1 = null;
String text2 = null;
for (int i = 1; i < ylist.size(); i++) {
for (int j = 1; j < xlist.size(); j++) {
if (i == 1) {
//第一行数据
if (j > 0 && xlist.size() - 1 > j) {
Rect rect1 = new Rect(cellWidth * j, centerY + 2 * cellHeight, cellWidth * j + cellWidth / 2, centerY + 3 * cellHeight);
Rect rect2 = new Rect(cellWidth * j + cellWidth / 2, centerY + 2 * cellHeight, cellWidth * (j + 1), centerY + 3 * cellHeight);
text1 = (String) dataMap.get(1).get(2 * (j - 1));
text2 = (String) dataMap.get(1).get(2 * (j - 1) + 1);
addDataText(canvas, text1, fontSize - 5-(xlist.size()-4)*2, rect1);
addDataText(canvas, text2, fontSize - 5-(xlist.size()-4)*2, rect2);
} else {
Rect rectPercent = new Rect(cellWidth * j, centerY + cellHeight, cellWidth * (j + 1), centerY + 3 * cellHeight);
text1 = (String) dataMap.get(1).get(2 * (j - 1));
addDataText(canvas, text1, fontSize + 1-(xlist.size()-4)*2, rectPercent);
}
} else {
//第2.。行数据
if (dataMap.get(i) == null) { //处理空白行数据
continue;
}
if (j > 0 && xlist.size() - 1 > j) {
Rect rect1 = new Rect(cellWidth * j, centerY + cellHeight + 2 * cellHeight * (i - 1), cellWidth * j + cellWidth / 2, centerY + cellHeight + 2 * cellHeight * (i - 1) + 2 * cellHeight);
Rect rect2 = new Rect(cellWidth * j + cellWidth / 2, centerY + cellHeight + 2 * cellHeight * (i - 1), cellWidth * (j + 1), centerY + cellHeight + 2 * cellHeight * (i - 1) + 2 * cellHeight);
text1 = (String) dataMap.get(i).get(2 * (j - 1));
text2 = (String) dataMap.get(i).get(2 * (j - 1) + 1);
addDataText(canvas, text1, fontSize - 5-(xlist.size()-4)*2, rect1);
addDataText(canvas, text2, fontSize - 5-(xlist.size()-4)*2, rect2);
} else {
Rect rectPercent = new Rect(cellWidth * j, centerY + cellHeight + 2 * cellHeight * (i - 1), cellWidth * (j + 1), centerY + cellHeight + 2 * cellHeight * (i));
text1 = (String) dataMap.get(i).get(2 * (j - 1));
addDataText(canvas, text1, fontSize + 1-(xlist.size()-4)*2, rectPercent);
}
}
}
}
}
/**
* @param canvas
* @param text 绘制的文本
* @param fontSize 文本大小
* @param targetRect 绘制的矩形区域,这里是居中显示
*/
private void addDataText(Canvas canvas, String text, float fontSize, Rect targetRect) {
/* Rect rectText = new Rect();
textPaint.getTextBounds(text.toString(), 0, text.toString().length(), rectText); // 获取字符串的宽高
canvas.save();
canvas.clipRect(rect); // 屏幕裁剪区域
if (indexY == 0) {
canvas.drawText(text, indexX * cellWidth + cellWidth / 2, centerY + (cellHeight + rectText.height()) / 2, textPaint);
} else {
canvas.drawText(text, indexX * cellWidth + cellWidth / 2, centerY + cellHeight * (indexY) + (indexY - 1) * 2 * cellHeight + (2*cellHeight + rectText.height()) / 2, textPaint);
}
canvas.restore();*/
textPaint.setTextSize(dpToPx(context, fontSize));
textPaint.setColor(Color.BLACK);
Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
int baseline = targetRect.top + (targetRect.bottom - targetRect.top) / 2 - (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.top;
// 下面这行是实现水平居中,drawText对应改为传入targetRect.centerX()
textPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(text, targetRect.centerX(), baseline, textPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureHeight(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureHeight(int heightMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
switch (specMode) {
case MeasureSpec.AT_MOST: //父wrap
// Log.e(TAG, "子容器可以是声明大小内的任意大小");
// Log.e(TAG, "大小为:" + specSize);
result = specSize;
break;
case MeasureSpec.EXACTLY: //子40dp=80px
// Log.e(TAG, "父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间");
// Log.e(TAG, "大小为:" + specSize);
result = specSize;
break;
case MeasureSpec.UNSPECIFIED: //父容器wrap ,子1999dp
// Log.e(TAG, "父容器对于子容器没有任何限制,子容器想要多大就多大");
// Log.e(TAG, "大小为:" + specSize);
result = 1500;
break;
default:
break;
}
return result;
}
// private void testText(Canvas canvas) {
// Rect targetRect = new Rect(50, 50, 700, 200);
// Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
// paint.setStrokeWidth(3);
// paint.setTextSize(30);
// String testString = "测试:ijkJQKA:1234";
// paint.setColor(Color.CYAN);
// canvas.drawRect(targetRect, paint);
// paint.setColor(Color.RED);
// Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); //一定要在paint参数设置后再调用
//
// //fon.top的参考值是baseline,它在baseline的上方,固为负值
// int baseline = targetRect.top + (targetRect.bottom - targetRect.top) / 2 - (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.top;
// // 下面这行是实现水平居中,drawText对应改为传入targetRect.centerX()
// paint.setTextAlign(Paint.Align.CENTER);
// canvas.drawText(testString, targetRect.centerX(), baseline, paint);
// }
// int lastX = 0;
// int lastY = 0;
// @Override
// public boolean onTouchEvent(MotionEvent event) { //使view可以跟随手指滑动
//
// //获取到手指处的横坐标和纵坐标
// int x = (int) event.getX();
// int y = (int) event.getY();
//
//
// switch (event.getAction()) {
// case MotionEvent.ACTION_DOWN:
//
// lastX = x;
// lastY = y;
//
// break;
//
// case MotionEvent.ACTION_MOVE:
//
// //计算移动的距离
// int offX = x - lastX;
// int offY = y - lastY;
// //调用layout方法来重新放置它的位置
// layout(getLeft() + offX, getTop() + offY,
// getRight() + offX, getBottom() + offY);
//
// break;
// }
//
// return true;
// }
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dpToPx(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
//布局
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_cell_chat"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:overScrollMode="never"
android:scrollbars="none"
tools:context="cn.jason.com.chatdemo.CellChatActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerInParent="true"
android:onClick="click_ADD"
android:text="ADD DATA" />
<cn.jason.com.chatdemo.CellChatView
android:id="@+id/cellChat"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
</ScrollView>
//在activity中
package cn.jason.com.chatdemo;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import java.util.ArrayList;
public class CellChatActivity extends AppCompatActivity {
private CellChatView cellChat;
private String TAG = "CellChat>>";
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 11) {
cellChat.postInvalidate();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cell_chat);
ActionBar actionBar = getSupportActionBar();
actionBar.setTitle("添加数据");
actionBar.hide();
cellChat= (CellChatView) findViewById(R.id.cellChat);
cellChat.xlist.add("科目"); //横向标题
cellChat.xlist.add("应上课时");
cellChat.xlist.add("实上课时");
cellChat.xlist.add("上课率");
// cellChat.xlist.add("上课率");
cellChat.ylist.add("科目"); //竖向标题
cellChat.ylist.add("钢琴");
cellChat.ylist.add("拉丁");
cellChat.ylist.add("合计");
cellChat.ylist.add("kk");
cellChat.ylist.add("kk");
cellChat.ylist.add("kk");
cellChat.ylist.add("kk");
cellChat.ylist.add("kk");
cellChat.ylist.add("kk");
cellChat.ylist.add("kk");
cellChat.ylist.add("kk");
cellChat.fxlist.add("个体课"); //第二行的小标题,index=1的行
cellChat.fxlist.add("集体课");
cellChat.fxlist.add("个体课");
cellChat.fxlist.add("集体课");
// mHandler.sendEmptyMessage(11);
//测试 内存泄漏 的列子,没什么用
// ActivityManager.getInstance().addActivity(this);
}
int i = 0;
public void click_ADD(View view) {
cellChat.isAddData = true;
i += 1;
//用单个list添加完数据clear(),发现每一行都用同一个list,clear后新加入的数据无效,暂时用多个list
ArrayList<String> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
ArrayList<String> list3 = new ArrayList<>();
ArrayList<String> list4 = new ArrayList<>();
list1.add(String.valueOf(i + 10));
list1.add("10");
list1.add("10");
list1.add("10");
list1.add("19%");
cellChat.dataMap.put(1, list1);
list2.add("20");
list2.add("10");
list2.add("10");
list2.add("10");
list2.add("29%");
// cellChat.dataMap.put(2, list2);
list3.add("30");
list3.add("10");
list3.add("10");
list3.add("10");
list3.add("39%");
cellChat.dataMap.put(3, list3);
// list4.add("40");
// list4.add("10");
// list4.add("10");
// list4.add("10");
// list4.add("49%");
// cellChat.dataMap.put(4, list4);
cellChat.postInvalidate();
}
}
这里我说一下,原本做本图表时只要4列4行固定的,不能有空白数据的单元格,在屏幕一半的位置开始。后来想多适配些场景,代码就没有简化,方法的调用参数填充都是按计算方式来,没有简单化成简式,所以有些地方比较长。如今图表可以多行多列,可滑动,能够简单处理空白标题和空白数据。
本图表还为大家添加了一个 内存泄漏的库使用
使用内存泄漏检测器
1.MyApplication的increase中 …if (LeakCanary.isInAnalyzerProcess(this)) { // This process is dedicated to LeakCanary for heap analysis. // You should not init your app in this process. return; } LeakCanary.install(this);
2.gradle …debugCompile ‘com.squareup.leakcanary:leakcanary-android:1.5.1’ releaseCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:1.5.1’ testCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:1.5.1’
不足:图表的高度我并没有细心的用准确的算法,所以最后会有白的地方,头部的actionbar高度和padding等都没算;填充数据的方式好笨,每填充一行内容就要new一个ArrayList,在做的时候就算保存到view的数据集datamap后再clear再加入新的数据,汇报空指针,这里应该是内存地址的引用直接将保存的数据也clear()了,希望大家可以说出好的数据填充方式。
亮点:图表可以2级标题,多行多列可滑动。
注意事项:开发中720p的手机可以直接设置dp,但到了1080p就不可以,整个view会变形。大家需要将用到的dp值全部转为dx(分辨率),dp*2=dp;文字居中在文中大家以后可以调用我的方法就可,这是最完美最准确的方式。
初次写稿,难免有考虑不周的地方,希望大家共同进步,指导意见。