最近项目中有用到折线柱状图,带渐变色的圆角矩形。听起来好像很可怕的样子。。。但是做安卓开发,自定义控件又是我们必不可少需要具备的技能,所以就花了两天时间写了三个柱状图的控件。
效果图如下:
这篇博客我就讲讲最上面的控件的绘制思路,下面的两个控件的内容基本上都包括在第一个里面了。
我们拿到一个需求需要慢慢分析,把大的需求拆分成一个一个的小需求,那么我就来分析一下这个自定义控件的思路吧。
首先我们分析需要绘制的部分:
1.绘制左边的纵坐标值,从底部往上绘制。
2.绘制背景色。
3.绘制日期上边的灰色横线。
4.绘制控件下面的日期值。
5.绘制柱状图。
6.绘制柱状图相连的折线。
7.绘制选中的灰色柱状图。
8.绘制选中的柱状图上面的小箭头图片。
其他需求分析:
9.柱状图的点击事件。
10.控件的滑动事件。
经过我的拆分完成此自定义控件需要完成以上10个小需求。
这里我先说说我遇到的坑,因为我这边的滑动事件是平移整个画布,一开始我把上面1-8个步骤绘制在一个控件中,会发现我向左滑动时把左边的数值也给滑动出去了。(当然也可以在我滑动时不从绘制左边的值,但是柱子还是会挡住左边的值,我们的需求是以左边的值为边界,不能让柱状图超过左边的值,所以为了完成这个需求我就把绘制也拆分成了两个部分)
我把1、2、3步绘制在一个通用的控件中,把剩余步骤绘制在另外一个控件中。
先来说说左边的通用控件:(HistogramLeftView)
public class HistogramLeftView extends View {
private int mWidth;//控件宽度
private int mHeight;//控件高度
//左边数据值的间距
private int valueSpacing = 20;
//普通画笔的宽度
private int paintWidth = 2;
//数据文字的大小
private int valueSize = 18;
//画笔,设置抗锯齿
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//左边的值距离控件左边及右边的间距
private int leftForViewSpacing = 20;
//控件的宽度和高度的比例
private float viewSizeScale = 0.7f;
//底部线条距离底部的距离
private int lineForViewBottom = 40;
public HistogramLeftView(Context context, AttributeSet attrs) {
super(context, attrs);
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mWidth = windowManager.getDefaultDisplay().getWidth();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mHeight = (int) (mWidth * viewSizeScale);
setMeasuredDimension(mWidth, mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.parseColor("#e6e6e6"));
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.parseColor("#adabac"));
paint.setStrokeWidth(paintWidth);
canvas.drawLine(leftForViewSpacing, (mHeight - lineForViewBottom), (mWidth - leftForViewSpacing), (mHeight - lineForViewBottom), paint);
drawLeftValueText(canvas);
}
/**
* 绘制左边的文字
*/
private void drawLeftValueText(Canvas canvas) {
paint.setColor(Color.parseColor("#b3b3b3"));
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(valueSize);
paint.setStrokeWidth(paintWidth);
for (int i = 3; i <= 18; i += 3) {
canvas.drawText(i + "", leftForViewSpacing, (mHeight - valueSpacing * i) - (lineForViewBottom + paintWidth), paint);
}
}
}
以上就是左边控件的代码:因为我们这个自定义控件需要重新绘制所以只能继承自安卓原生的View。
因为我测量了美工给的图片,宽和高的比例是10:7。
所以就出来了这个变量
private float viewSizeScale = 0.7f;
因为我们这个控件需要占满整个屏幕,所以我就得动态测量到屏幕的宽度所以在初始化中就调用了
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mWidth = windowManager.getDefaultDisplay().getWidth();
在通过比例动态的算出控件的高度
然后在onMeasure()方法中调用:
setMeasuredDimension(mWidth, mHeight);
调用此方法就是把控件的大小强制的设置为我们想要的大小,不管你在xml中如何写改控件都是全屏的宽度。
当onMeasure()方法走完了就会去调用onDraw()方法
首先绘制背景色调用:
canvas.drawColor(Color.parseColor("#e6e6e6"));
这样就完成了我们的第二个步骤
然后绘制底部的线条:
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.parseColor("#adabac"));
paint.setStrokeWidth(paintWidth);
canvas.drawLine(leftForViewSpacing, (mHeight - lineForViewBottom), (mWidth - leftForViewSpacing), (mHeight - lineForViewBottom), paint);
这里设置画笔的样式,可以是空心的也可以是实心的,这个只针对画图形,对于我们这个画线来说没作用,然后设置画笔的颜色,也就是我们绘制的线段需要用到什么颜色。最后调用canvas的drawLine()方法,方法中的5个参数分别代表:线的起始点的x坐标点、线的起始点的y坐标点,线的终点的x坐标点,线的终点的y坐标点和画笔。
这样就完成了我们的第3个步骤
然后绘制左边的值:
调用了drawLeftValueText(canvas)方法
private void drawLeftValueText(Canvas canvas) {
paint.setColor(Color.parseColor("#b3b3b3"));
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(valueSize);
paint.setStrokeWidth(paintWidth);
for (int i = 3; i <= 18; i += 3) {
canvas.drawText(i + "", leftForViewSpacing, (mHeight - valueSpacing * i) - (lineForViewBottom + paintWidth), paint);
}
}
这里要注意一下绘制文字时,需要设置setTextSize();设置文字的大小,因为我这边是需要绘制从3开始的6个值,所以我这里就调用到了循环绘制,这里可以看到绘制左边值的时候x坐标是不需要改变的,只需要改变Y坐标。最后调用canvas.drawText();该方法中的4个参数分别代表:需要绘制的文本,需要绘制文本的左上角x坐标,需要绘制文本的左上角y坐标和画笔。
这样就完成了我们的第一个步骤
左边的通用控件也就绘制完成。(也就是不移动的部分绘制完成)
下面就分析分析右边可以滑动的控件,也是这个控件的重头戏(DiscountedGradientView)还是一样的先把代码方法上去,嘿嘿
public class DiscountedGradientView extends View {
//一屏幕只有7根圆柱的模式
private static int SEVEN_MODE = 0;
//一屏幕有15根圆柱的模式
private static int FIFTEEN_MODE = 1;
//当前模式的标志值
private int currentMode = -1;
private int mWidth;//控件宽度
private int mHeight;//控件高度
//左边数据值的间距(一个刻度的距离)
private int valueSpacing = 20;
//画笔,设置抗锯齿
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//日期文字的大小
private int dateSize = 18;
//左边的值距离控件的间距
private int leftForViewSpacing = 20;
//控件的宽度和高度的比例
private float viewSizeScale = 0.7f;
//柱状图的宽度
private float pillarWidth;
//柱状图的间距大小
private float pillarSpacing;
//数据集合
private List<Values> values;
//资源文件
private Drawable shape1;
private Drawable shape2;
private Drawable shape3;
private Drawable shape4;
private Bitmap bitmap;
//柱状图和数据捆绑的集合
private List<Task> tasks = new ArrayList<>();
//监听器
private OnItemPillarClickListeners onItemPillarClickListeners;
//是否点击
private boolean isClick;
//手指按下时的位置
private float startX;
private float startY;
private float moveX;
private int currentPos = -1;
private float currentX = -1;
public OnItemPillarClickListeners getOnItemPillarClickListeners() {
return onItemPillarClickListeners;
}
public void setOnItemPillarClickListeners(OnItemPillarClickListeners onItemPillarClickListeners) {
this.onItemPillarClickListeners = onItemPillarClickListeners;
}
public DiscountedGradientView(Context context) {
this(context, null);
}
public DiscountedGradientView(Context context, AttributeSet attrs) {
super(context, attrs);
setData();
//3种shape的资源
shape1 = context.getResources().getDrawable(R.drawable.shape1);
shape2 = context.getResources().getDrawable(R.drawable.shape2);
shape3 = context.getResources().getDrawable(R.drawable.shape3);
shape4 = context.getResources().getDrawable(R.drawable.shape4);
bitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.bottom);
//动态获取屏幕宽度
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mWidth = windowManager.getDefaultDisplay().getWidth();
//设定屏幕高度
mHeight = (int) (mWidth * viewSizeScale);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取父容器给到的最大宽度
mWidth = MeasureSpec.getSize(widthMeasureSpec);
//柱状图宽度
pillarWidth = (float) mWidth / 30;
if (currentMode == SEVEN_MODE) {
//当一屏只有7根柱子时,间距的宽度
pillarSpacing = (mWidth - (values.size() * pillarWidth)) / (values.size());
} else {
//当一屏有15根柱子时,间距的宽度
pillarSpacing = (float) mWidth / 30;
}
//强制把控件设置成我们想要的固定的大小
setMeasuredDimension(mWidth, mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//平移画布(平移的距离时moveX,为负数时为向左移动,为正数时为向右移动)
canvas.translate(moveX, 0);
//画柱状图
drawPillarView(canvas);
//画柱状图中虚线
drawDottedLine(canvas);
//画日期值
drawDate(canvas);
}
/**
* 绘制柱状图
*
* @param canvas
*/
private void drawPillarView(Canvas canvas) {
//每次滑动都会调用绘制的方法(所以应该清空集合)
tasks.clear();
//添加灰色选中背景
for (int i = 0; i < values.size(); i++) {
if (currentPos == i) {
//灰色阴影的矩形范围
Rect rect2 = new Rect((int) currentX, mHeight - (42 + leftForViewSpacing * 18), (int) (currentX + pillarWidth), mHeight - 42);
shape4.setBounds(rect2);
shape4.draw(canvas);
//绘制箭头图片,选择图片的哪个位置
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
//箭头图片在控件中的矩形位置
Rect rect3 = new Rect((int) currentX, mHeight - (42 + leftForViewSpacing * 18) - 30, (int) currentX + 23, mHeight - (42 + leftForViewSpacing * 18) - 10);
canvas.drawBitmap(bitmap, rect, rect3, paint);
}
//渐变色的柱状图位置确认
Rect rect5 = new Rect((int) (i * (pillarWidth + pillarSpacing) + pillarSpacing / 2), mHeight - values.get(i).getMax() * 20 - 42, (int) ((1 + i) * pillarWidth + i * pillarSpacing + pillarSpacing / 2), mHeight - values.get(i).getMin() * 20 - 42);
//根据不同的情况显示不同的渐变色
if (values.get(i).getMax() >= 10 && values.get(i).getMax() < 13) {
shape3.setBounds(rect5);
shape3.draw(canvas);
} else if (values.get(i).getMax() >= 13) {
shape1.setBounds(rect5);
shape1.draw(canvas);
} else {
shape2.setBounds(rect5);
shape2.draw(canvas);
}
//把数据和矩形绑在一起
tasks.add(new Task(rect5, values.get(i)));
}
}
/**
* 绘制虚线
*
* @param canvas
*/
private void drawDottedLine(Canvas canvas) {
paint.setColor(Color.parseColor("#222222"));
paint.setStrokeWidth(0.5f);
paint.setPathEffect(new DashPathEffect(new float[]{5, 5}, 0));
float average2 = 0;
for (int i = 0; i < values.size(); i++) {
Values values = this.values.get(i);
float average = (float) (values.getMax() + values.getMin()) / 2;
if (i + 1 < this.values.size()) {
Values v2 = this.values.get(i + 1);
average2 = (float) (v2.getMax() + v2.getMin()) / 2;
}
canvas.drawLine(i * (pillarWidth + pillarSpacing) + pillarSpacing / 2 + pillarWidth, (mHeight - valueSpacing * average) - 42, ((1 + i) * pillarSpacing + pillarWidth * i + pillarSpacing / 2 + pillarWidth), (mHeight - valueSpacing * average2) - 42, paint);
}
}
/**
* 绘制日期
*
* @param canvas
*/
private void drawDate(Canvas canvas) {
paint.setColor(Color.parseColor("#9e9c9d"));
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(dateSize);
for (int i = 0; i < values.size(); i++) {
canvas.drawText(values.get(i).getDate() + "日", i * pillarWidth + pillarSpacing * i + (pillarSpacing / 2), mHeight - 10, paint);
}
}
/**
* 设置数据
*/
public void setData() {
this.values = new ArrayList<>();
Random random = new Random();
for (int i = 1; i <= 30; i++) {
Values v = new Values(random.nextInt(13) + 5, random.nextInt(5), i);
this.values.add(v);
}
if (values.size() <= 7) {
currentMode = SEVEN_MODE;
} else if (values.size() > 15) {
currentMode = FIFTEEN_MODE;
} else {
currentMode = -1;
}
}
/**
* 覆盖手势
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = x;
startY = y;
isClick = true;
Log.e("lee", "ACTION_DOWN" + x);
break;
case MotionEvent.ACTION_MOVE:
if (currentMode == SEVEN_MODE || currentMode == -1) {
return true;
}
if (Math.abs(x - startY) > 10) {//视为滑动
isClick = false;
}
//计算滑动的距离
moveX = moveX + (x - startX);
if (moveX >= 0) {
moveX = 0;
}
if (moveX <= -((values.size() - 15) * (pillarWidth + pillarSpacing)) + pillarSpacing / 2) {
moveX = -((values.size() - 15) * (pillarWidth + pillarSpacing) + pillarSpacing / 2);
}
Log.e("lee", "ACTION_MOVE" + x);
this.invalidate();
break;
case MotionEvent.ACTION_UP:
Log.e("lee", "ACTION_UP" + x);
if (isClick) {
for (int i = 0; i < tasks.size(); i++) {
Rect rect = tasks.get(i).getRect();
if (rect.left < (x - moveX) && rect.right > (x - moveX)) {
currentPos = i;
currentX = rect.left;
postInvalidate();
if (getOnItemPillarClickListeners() != null) {
onItemPillarClickListeners.clickItem(tasks.get(i).getValues());
}
}
}
}
break;
}
return true;
}
/**
* 回调接口
*/
public interface OnItemPillarClickListeners {
//回调方法
void clickItem(Values Values);
}
}
仔细的小伙伴会发现有一个图是一个屏幕只有7根柱状图的,有的是一个屏幕15根柱状图的。
就是通过下面的变量来控制模式。
//一屏幕只有7根圆柱的模式
private static int SEVEN_MODE = 0;
//一屏幕有15根圆柱的模式
private static int FIFTEEN_MODE = 1;
//当前模式的标志值
private int currentMode = -1;
在setData的时候判断模式:
/**
* 设置数据
*/
public void setData() {
this.values = new ArrayList<>();
Random random = new Random();
for (int i = 1; i <= 30; i++) {
Values v = new Values(random.nextInt(13) + 5, random.nextInt(5), i);
this.values.add(v);
}
//判断模式
if (values.size() <= 7) {
currentMode = SEVEN_MODE;
} else if (values.size() > 15) {
currentMode = FIFTEEN_MODE;
} else {
currentMode = -1;
}
}
这里在初始化的时候引用了shape资源文件
//3种shape的资源
shape1 = context.getResources().getDrawable(R.drawable.shape1);
shape2 = context.getResources().getDrawable(R.drawable.shape2);
shape3 = context.getResources().getDrawable(R.drawable.shape3);
shape4 = context.getResources().getDrawable(R.drawable.shape4);
//箭头图片
bitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.bottom);
在onMeasure()方法中和第一个控件有些不同的地方:
//获取父容器给到的最大宽度
mWidth = MeasureSpec.getSize(widthMeasureSpec);
//柱状图宽度
pillarWidth = (float) mWidth / 30;
if (currentMode == SEVEN_MODE) {
//当一屏只有7根柱子时,间距的宽度
pillarSpacing = (mWidth - (values.size() * pillarWidth)) / (values.size());
} else {
//当一屏有15根柱子时,间距的宽度
pillarSpacing = (float) mWidth / 30;
}
//强制把控件设置成我们想要的固定的大小
setMeasuredDimension(mWidth, mHeight);
改控件获取宽度是通过获取到父容器给到的最大宽度,因为我给该控件设置了magin值,但是高度的话还是要屏幕的宽度乘以比例。所以在初始化的时候就把高度定死了。这里通过判断动态的计算两根柱子之间的间距。
然后调用onDraw();方法
//平移画布(平移的距离时moveX,为负数时为向左移动,为正数时为向右移动)
canvas.translate(moveX, 0);
//画柱状图
drawPillarView(canvas);
//画柱状图中虚线
drawDottedLine(canvas);
//画日期值
drawDate(canvas);
就会调用以上几个方法(暂时先忽略canvas.translate(moveX, 0);方法)
画柱状图的方法:
/**
* 绘制柱状图
*
* @param canvas
*/
private void drawPillarView(Canvas canvas) {
//每次滑动都会调用绘制的方法(所以应该清空集合)
tasks.clear();
//添加灰色选中背景
for (int i = 0; i < values.size(); i++) {
if (currentPos == i) {
//灰色阴影的矩形范围
Rect rect2 = new Rect((int) currentX, mHeight - (42 + leftForViewSpacing * 18), (int) (currentX + pillarWidth), mHeight - 42);
shape4.setBounds(rect2);
shape4.draw(canvas);
//绘制箭头图片,选择图片的哪个位置
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
//箭头图片在控件中的矩形位置
Rect rect3 = new Rect((int) currentX, mHeight - (42 + leftForViewSpacing * 18) - 30, (int) currentX + 23, mHeight - (42 + leftForViewSpacing * 18) - 10);
canvas.drawBitmap(bitmap, rect, rect3, paint);
}
//渐变色的柱状图位置确认
Rect rect5 = new Rect((int) (i * (pillarWidth + pillarSpacing) + pillarSpacing / 2), mHeight - values.get(i).getMax() * 20 - 42, (int) ((1 + i) * pillarWidth + i * pillarSpacing + pillarSpacing / 2), mHeight - values.get(i).getMin() * 20 - 42);
//根据不同的情况显示不同的渐变色
if (values.get(i).getMax() >= 10 && values.get(i).getMax() < 13) {
shape3.setBounds(rect5);
shape3.draw(canvas);
} else if (values.get(i).getMax() >= 13) {
shape1.setBounds(rect5);
shape1.draw(canvas);
} else {
shape2.setBounds(rect5);
shape2.draw(canvas);
}
//把数据和矩形绑在一起
tasks.add(new Task(rect5, values.get(i)));
}
}
这里主要讲讲后半截:调用shape3.setBounds();方法把矩形放进去确认位置,然后根据不同的情况调用不同的shape3.draw(canvas);方法绘制出来。其实渐变色和矩形是在shape的xml文件中写好了的。
这样也就完成了第5个步骤
绘制虚线:
/**
* 绘制虚线
*
* @param canvas
*/
private void drawDottedLine(Canvas canvas) {
paint.setColor(Color.parseColor("#222222"));
paint.setStrokeWidth(0.5f);
float average2 = 0;
for (int i = 0; i < values.size(); i++) {
Values values = this.values.get(i);
float average = (float) (values.getMax() + values.getMin()) / 2;
if (i + 1 < this.values.size()) {
Values v2 = this.values.get(i + 1);
average2 = (float) (v2.getMax() + v2.getMin()) / 2;
}
canvas.drawLine(i * (pillarWidth + pillarSpacing) + pillarSpacing / 2 + pillarWidth, (mHeight - valueSpacing * average) - 42, ((1 + i) * pillarSpacing + pillarWidth * i + pillarSpacing / 2 + pillarWidth), (mHeight - valueSpacing * average2) - 42, paint);
}
}
先设置画笔颜色,也就是线段的颜色,然后设置画笔粗细,也就是线段的宽度,这里主要介绍一下canvas.drawLine()这个方法上面已经说过了就是绘制线段。
这样也就完成了第6个步骤
绘制日期:
/**
* 绘制日期
*
* @param canvas
*/
private void drawDate(Canvas canvas) {
paint.setColor(Color.parseColor("#9e9c9d"));
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(dateSize);
for (int i = 0; i < values.size(); i++) {
canvas.drawText(values.get(i).getDate() + "日", i * pillarWidth + pillarSpacing * i + (pillarSpacing / 2), mHeight - 10, paint);
}
}
这里也没什么好说的绘制文本上面也已经说过了,只不过里面的参数是通过计算的,上面也有注释。
这样也就完成了第4个步骤:
所有有关绘制的流程完成了
下面就讲事件处理的逻辑:
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = x;
startY = y;
isClick = true;
Log.e("lee", "ACTION_DOWN" + x);
break;
case MotionEvent.ACTION_MOVE:
if (currentMode == SEVEN_MODE || currentMode == -1) {
return true;
}
if (Math.abs(x - startY) > 10) {//视为滑动
isClick = false;
}
//计算滑动的距离
moveX = moveX + (x - startX);
if (moveX >= 0) {
moveX = 0;
}
if (moveX <= -((values.size() - 15) * (pillarWidth + pillarSpacing)) + pillarSpacing / 2) {
moveX = -((values.size() - 15) * (pillarWidth + pillarSpacing) + pillarSpacing / 2);
}
Log.e("lee", "ACTION_MOVE" + x);
this.invalidate();
break;
case MotionEvent.ACTION_UP:
Log.e("lee", "ACTION_UP" + x);
if (isClick) {
for (int i = 0; i < tasks.size(); i++) {
Rect rect = tasks.get(i).getRect();
if (rect.left < (x - moveX) && rect.right > (x - moveX)) {
currentPos = i;
currentX = rect.left;
postInvalidate();
if (getOnItemPillarClickListeners() != null) {
onItemPillarClickListeners.clickItem(tasks.get(i).getValues());
}
}
}
}
break;
}
return true;
}
大家都知道只要触碰了屏幕的话就会走到onTouchEvent();方法中。只不过会根据不同的手势走进不同的case中去。
1.当用户按下时先记录到了用户按下的坐标x和y,然后进入down事件中把startX,startY记录下来,然后判断点击事件的标识isClick就为true了,假设用户立马松开手了就会走到up事件中,此时isclick为true就会走里面的代码,然后遍历这个矩形以及数据的集合判断当前的x坐标属于哪个矩形中把矩形的最左边记录下来,把数据回调出去,然后调用postInvalidate();方法。调用了此方法后会重新走一遍onDraw();方法了,其他的不说了主要说这一段代码:
if (currentPos == i) {
//灰色阴影的矩形范围
Rect rect2 = new Rect((int) currentX, mHeight - (42 + leftForViewSpacing * 18), (int) (currentX + pillarWidth), mHeight - 42);
shape4.setBounds(rect2);
shape4.draw(canvas);
//绘制箭头图片,选择图片的哪个位置
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
//箭头图片在控件中的矩形位置
Rect rect3 = new Rect((int) currentX, mHeight - (42 + leftForViewSpacing * 18) - 30, (int) currentX + 23, mHeight - (42 + leftForViewSpacing * 18) - 10);
canvas.drawBitmap(bitmap, rect, rect3, paint);
}
在up事件中记录下了currentPos的值,重绘制时来匹配用户点击的是哪个矩形然后绘制灰的矩形,绘制上面的小箭头。
回到onTouchEvent();方法中:
当用户点击了屏幕时,还是一样先记录 x和y 然后走到down事件中记录下startX和srartY的位置,该值是固定的。如果当用户滑动屏幕时就会走到move事件中去,此时先要判断是不是可以滑动的模式,如果不是直接返回true把事件消费了。如果滑动的距离大于10个像素的话视为滑动,先把点击事件的值改为false,让待会滑动完之后抬手时不会执行up事件中的代码。然后在记录下滑动的距离滑动的距离就是(x - startX);值为正数时往右滑,反之,往左滑,这里设置了一个边距,不可能让他永远滑下去,所以当滑动的值>=0时就是最左边了不允许在往左滑了,反之右边的边距也是手动计算出来的,然后调用 this.invalidate();方法重新绘制。该控件也就绘制完成了。
把其中一个shape:代码展示出来
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="90"
android:centerColor="#4b78a2"
android:endColor="#4e2f7c"
android:gradientRadius="800"
android:startColor="#4cc2c4" />
<corners android:radius="20dp" />
</shape>
activity类:
public class MainActivity extends AppCompatActivity implements DiscountedGradientView.OnItemPillarClickListeners{
private DiscountedGradientView view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
view = (DiscountedGradientView) findViewById(R.id.view);
view.setOnItemPillarClickListeners(this);
}
@Override
public void clickItem(Values Values) {
Toast.makeText(MainActivity.this, "第" + Values.getDate() + "天" + "最大值为" + Values.getMax() + "最小值为" + Values.getMin(), Toast.LENGTH_SHORT).show();
}
}
xml文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.lee.myapplication.MainActivity">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.example.lee.myapplication.HistogramLeftView
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.example.lee.myapplication.DiscountedGradientView
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="25dp"
android:layout_marginRight="25dp" />
</FrameLayout>
</LinearLayout>