欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android开发之天气趋势折线图

程序员文章站 2024-03-06 22:22:38
先来看下效果: 控件内容比较简单,就是一个普通的折线图,上下分别带有数字,点击的时候显示当天温度的差值。  创建一个类继承自view,并添加两个构造方法:...

先来看下效果:

Android开发之天气趋势折线图

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

创建一个类继承自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能有所帮助。