之前写了个天气APP,带4天预报和5天历史信息。所以想着要不要加一个折线图来显示一下天气变化趋势,难得有空,就写了一下,这里做些记录,脑袋不好使容易忘事。

先放一下效果:

控件内容比较简单,就是一个普通的折线图,上下分别带有数字,点击的时候显示当天温度的差值。


创建一个类继承自View,并添加两个构造方法:

public class TrendGraph extends View {
public TrendGraph(Context context) { // 在java代码中创建调用
super(context);
} public TrendGraph(Context context, AttributeSet attrs) { // 在xml中创建调用
super(context, attrs);
}
}

因为这里不需要考虑wrap_content的情况,所以onMeasure方法不需重写,关键的是onDraw,而onDraw方法其实也不困难,只需要确定好每个点的具体位置就好,因为连线也是需要点的坐标,代码比较啰嗦,可以略过:

     @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mElements == null || mElements.size() == 0) {
return;
}
double max_up = getMaxUp();
double min_down = getMinDown();
canvas.setDrawFilter(mDrawFilter);
mPaint.setStrokeWidth(lineWeith);
float width = getWidth();
float grap = width / mElements.size();
float textSize = mTextPaint.getTextSize();
int textMargin = circleRadius * 2;
float margin_top = textSize + 2 * textMargin;
Log.d(TAG, "onDraw: " + margin_top + "|" + textSize);
float height = getHeight() - 2 * margin_top; for (int i = 0; i < mElements.size() - 1; i++) {
float startX = i * grap + grap / 2;
float stopX = (i + 1) * grap + grap / 2;
float startY = (float) (max_up - mElements.get(i).getUp()) / (float) (max_up -
min_down) * height + margin_top;
float stopY = (float) (max_up - mElements.get(i + 1).getUp()) / (float) (max_up -
min_down) * height + margin_top; canvas.drawText((int) mElements.get(i).getUp() + "℃", startX - textSize, startY -
textMargin, mTextPaint);
canvas.drawCircle(startX, startY, circleRadius, mPaint);
canvas.drawLine(startX, startY, stopX, stopY, mPaint);
if (i == mElements.size() - 2) {
canvas.drawText((int) mElements.get(i + 1).getUp() + "℃", stopX - textSize, stopY
- textMargin, mTextPaint);
canvas.drawCircle(stopX, stopY, circleRadius, mPaint);
} startY = (float) (max_up - mElements.get(i).getDown()) / (float) (max_up - min_down) *
height + margin_top;
stopY = (float) (max_up - mElements.get(i + 1).getDown()) / (float) (max_up -
min_down) * height + margin_top;
canvas.drawText((int) mElements.get(i).getDown() + "℃", startX - textSize, startY +
textSize + textMargin, mTextPaint);
canvas.drawCircle(startX, startY, circleRadius, mPaint);
canvas.drawLine(startX, startY, stopX, stopY, mPaint);
if (i == mElements.size() - 2) {
canvas.drawText((int) mElements.get(i + 1).getDown() + "℃", stopX - textSize,
stopY + textSize + textMargin, mTextPaint);
canvas.drawCircle(stopX, stopY, circleRadius, mPaint);
}
}
}

考虑到需要允许用户进行简单的设置,例如点的大小,文字大小等等,所以定义一些自定义属性(res/values/attr.xml):

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TrendGraph">
<attr name="lineWidth" format="dimension"/>
<attr name="circleRadius" format="dimension" />
<attr name="textSize" format="dimension" />
<attr name="textColor" format="reference" />
</declare-styleable>
</resources>

format指该属性的格式,指定为dimension则是尺寸,取值单位是dp、sp或px等等,而reference则是引用,即一般在xml中引用其他资源的写法,如@string/app_name。还有其他类型,可以自行查找文档。

对自定义属性进行解析得到,这个解析需要在上面定义的第二个构造方法中进行,代码如下:

    public TrendGraph(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.TrendGraph);
circleRadius = array.getDimensionPixelSize(R.styleable.TrendGraph_circleRadius, 5);
lineWeith = array.getDimensionPixelSize(R.styleable.TrendGraph_lineWidth, 3);
mTextPaint.setTextSize(array.getDimensionPixelSize(R.styleable.TrendGraph_textSize, 35));
mTextPaint.setColor(array.getColor(R.styleable.TrendGraph_textColor, Color.BLACK));
array.recycle();
}

getDimensionPixelSize方法则是通过传入的值,转换为具体的像素(px)值,也就免去我们手动转换的麻烦。但是要注意,其中的defaultValue依然是px。

接着,就可以通过xml指定这些属性,在布局中加入命名空间:

xmlns:app="http://schemas.android.com/apk/res-auto"

则Android Studio会自动引入,并且可以补全得到,具体使用:

    <com.fndroid.byweather.views.TrendGraph
android:id="@+id/tg"
android:layout_width="match_parent"
app:textColor="@color/colorAccent"
app:textSize="22sp"
app:circleRadius="2dp"
android:layout_height="200dp"/>

最后,添加一个事件监听,在点击View的时候进行回调:

① 定义接口:

    public interface onItemClickListener{
void onItemClick(View view, Element element);
}

② 在View中添加接口对象,并设置setter方法:

public class TrendGraph extends View {

    private onItemClickListener mOnItemClickListener;

    // 省略代码

    public void setOnItemClickListener(onItemClickListener onItemClickListener) {
mOnItemClickListener = onItemClickListener;
} }

③ 处理onTouchEvent,重写该方法,代码如下:

    @Override
public boolean onTouchEvent(MotionEvent event) {
int viewWidth = getWidth();
int itemWidth = viewWidth / mElements.size();
int viewHeight = getHeight();
boolean isMove = false; // 界面中最外层为一个NestedScrollView,所以为了避免滑动时也触发,加入变量处理 switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
isMove = true;
break;
case MotionEvent.ACTION_UP:
if (!isMove){ // 判断只有点击时进行回调
int position = (int) (event.getX() / itemWidth); // 取得点击的位置
mOnItemClickListener.onItemClick(this, mElements.get(position)); // 回调
}
break;
} return true;
}

④ 在Activity中,进行监听设置,并处理:

  historyGraph.setOnItemClickListener(this);
    @Override
public void onItemClick(View view, TrendGraph.Element element) {
int dt = (int) (element.getUp() - element.getDown());
Snackbar.make(root, "当天温差为:" + dt + "℃", Snackbar.LENGTH_SHORT).show();
}

效果完成!欢迎大家关注交流。

Android开发学习之路-自定义控件(天气趋势折线图)的更多相关文章

  1. Android开发学习之路-RecyclerView滑动删除和拖动排序

    Android开发学习之路-RecyclerView使用初探 Android开发学习之路-RecyclerView的Item自定义动画及DefaultItemAnimator源码分析 Android开 ...

  2. Android开发学习之路--基于vitamio的视频播放器(二)

      终于把该忙的事情都忙得差不多了,接下来又可以开始good good study,day day up了.在Android开发学习之路–基于vitamio的视频播放器(一)中,主要讲了播放器的界面的 ...

  3. Android开发学习之路--Android Studio cmake编译ffmpeg

      最新的android studio2.2引入了cmake可以很好地实现ndk的编写.这里使用最新的方式,对于以前的android下的ndk编译什么的可以参考之前的文章:Android开发学习之路– ...

  4. Android开发学习之路--网络编程之xml、json

    一般网络数据通过http来get,post,那么其中的数据不可能杂乱无章,比如我要post一段数据,肯定是要有一定的格式,协议的.常用的就是xml和json了.在此先要搭建个简单的服务器吧,首先呢下载 ...

  5. Android开发学习之路--Activity之初体验

    环境也搭建好了,android系统也基本了解了,那么接下来就可以开始学习android开发了,相信这么学下去肯定可以把android开发学习好的,再加上时而再温故下linux下的知识,看看androi ...

  6. Android开发学习之路--Android系统架构初探

    环境搭建好了,最简单的app也运行过了,那么app到底是怎么运行在手机上的,手机又到底怎么能运行这些应用,一堆的电子元器件最后可以运行这么美妙的界面,在此还是需要好好研究研究.这里从芯片及硬件模块-& ...

  7. Android开发学习之路--MAC下Android Studio开发环境搭建

    自从毕业开始到现在还没有系统地学习android应用的开发,之前一直都是做些底层的驱动,以及linux上的c开发.虽然写过几个简单的app,也对android4.0.3的源代码做过部分的分析,也算入门 ...

  8. Android开发学习之路-记一次CSDN公开课

    今天的CSDN公开课Android事件处理重难点快速掌握中老师讲到一个概念我觉得不正确. 原话是这样的:点击事件可以通过事件监听和回调两种方法实现. 我一听到之后我的表情是这样的: 这跟我学的看的都不 ...

  9. Android开发学习之路-Android Studio开发小技巧

    上一次发过了一个介绍Studio的,这里再发一个补充下. 我们都知道,Android Studio的功能是非常强大的,也是很智能的.如果有人告诉你学Android开发要用命令行,你可以告诉他Andro ...

随机推荐

  1. Fis3的前端模块化之路[基础篇]

    Fis3版本:v3.4.22 fis3是一个构建工具 解决前端开发中自动化工具.性能优化.模块化框架.开发规范.代码部署.开发流程等问题. 安装 npm install -g fis3 运行 fis3 ...

  2. 在传统.NET Framework 上运行ASP.NET Core项目

    新的项目我们想用ASP.NET Core来开发,但是苦于我们历史的遗产很多,比如<使用 JavaScriptService 在.NET Core 里实现DES加密算法>,我们要估计等到.N ...

  3. salesforce 零基础学习(六十二)获取sObject中类型为Picklist的field values(含record type)

    本篇引用以下三个链接: http://www.tgerm.com/2012/01/recordtype-specific-picklist-values.html?m=1 https://github ...

  4. 【.net 深呼吸】限制执行代码的权限

    前面好几篇文章,老周都跟大伙伴们聊了跟应用程序域有关的话题,干脆咱们一聊到底吧,做学问就应该这样,有恒心. App Domain的创建新应用程序域的方法中,有一个特殊的重载: public stati ...

  5. trigger事件模拟

    事件模拟trigger 在操作DOM元素中,大多数事件都是用户必须操作才会触发事件,但有时,需要模拟用户的操作,来达到效果. 需求:页面初始化时触发搜索事件并获取input控件值,并打印输出(效果图如 ...

  6. Web开发安全之文件上传安全

    很长一段时间像我这种菜鸡搞一个网站第一时间反应就是找上传,找上传.借此机会把文件上传的安全问题总结一下. 首先看一下DVWA给出的Impossible级别的完整代码: <?php if( iss ...

  7. swift开发新项目总结

    新项目用swift3.0开发,现在基本一个月,来总结一下遇到的问题及解决方案   1,在确定新项目用swift后,第一个考虑的问题是用纯swift呢?还是用swift跟OC混编      考虑到新项目 ...

  8. Android之SharedPreferences数据存储

    一.SharedPreferences保存数据介绍 如果有想要保存的相对较小键值集合,应使用SharedPreferences API.SharedPreferences对象指向包含键值对的文件并提供 ...

  9. Android studio使用gradle动态构建APP(不同的包,不同的icon、label)

    最近有个需求,需要做两个功能相似的APP,大部分代码是一样的,只是界面不一样,以前要维护两套代码,比较麻烦,最近在网上找资料,发现可以用gradle使用同一套代码构建两个APP.下面介绍使用方法: 首 ...

  10. docker4dotnet #3 在macOS上使用Visual Studio Code和Docker开发asp.net core和mysql应用

    .net猿遇到了小鲸鱼,觉得越来越兴奋.本来.net猿只是在透过家里那田子窗看外面的世界,但是看着海峡对岸的苹果园越来越茂盛,实在不想再去做一只宅猿了.于是,.net猿决定搭上小鲸鱼的渡轮到苹果园去看 ...