Android简易天气App

public void onAnimationEnd(Animator animation) {
isAnimation = false;
}

@Override
public void onAnimationCancel(Animator animation) {

}

@Override
public void onAnimationRepeat(Animator animation) {

}
});
set.start();
}

arrayList()方法,除了保存数据外,将温度做个转换,因为初始是从平均值开始变的,mHighPercent在1s的时间内从0变为15日最高温度值,mHighPercent * (innerData.get(i).getHighTemp() – averageHigh) / (max – 0)可以做到在1s的时间内,将当日最高温度从平均值变为实际值,当日最低温度同理。

@SuppressWarnings(“PointlessArithmeticExpression”)
private void arrayList(ArrayList innerData, int max, int min, int averageHigh, int averageLow) {
high = averageHigh;
low = averageLow;

dataArray.clear();
//保存网络数据
dataArray.addAll(innerData);

for (int i = 0; i < innerData.size(); i++) {
//在1s的变化时间内,将值从平均值变为实际值
dataArray.get(i).setHighTemp((innerData.get(i).getHighTemp() - averageHigh) / (max - 0));
//在1s的变化时间内,将值从平均值变为实际值
dataArray.get(i).setLowTemp((averageLow - innerData.get(i).getLowTemp()) / (min - 0));
}
}

重写onMeasure()。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
measureWidth = widthSize;
measureHeight = heightSize;
//一个页面展示6天的温度信息,每一天为宽度为mTempWidth
mTempWidth = measureWidth / 6;
setMeasuredDimension(widthSize, heightSize);
}

重写onDraw()。

@SuppressLint(“DrawAllocation”)
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//为请求到网络数据时,页面显示文字
if (dataArray.size() <= 0) {
drawNoDataText(canvas);
} else {
float startX = mStartX + (mTempWidth / 2);
for (int i = 0; i < dataArray.size(); i++) {
//绘制天气图标
RectF rectF = new RectF(startX - 30, 300, startX + 30, 360);
canvas.drawBitmap(dataArray.get(i).getTypeBitmap(), null, rectF, mCurvePaint);

//绘制最高温度
float highTextWidth = mTempTextPaint.measureText((int)(high + mHighPercent * dataArray.get(i).getHighTemp()) + “”);
float highTextStartX = startX - highTextWidth / 2;
drawTempText(canvas, (int)(high + getHighTempByPercent(i)) + “”, highTextStartX, (140 - curve_ratio * getHighTempByPercent(i)));
//curve_ratio为可变值,用于调整显示效果。该值越大温度差效果越明显。
canvas.drawCircle(startX, (160 - curve_ratio * getHighTempByPercent(i)), 5, circlePaint);
//第一段曲线
if (i == 0) {
highPath.moveTo(startX, (160 - curve_ratio * getHighTempByPercent(i)));
highControlPt1X = startX + mTempWidth / 4;
highControlPt1Y = 160 - curve_ratio * getHighTempByPercent(i);
highControlPt2X = startX + (mTempWidth / 4) * 3;
highControlPt2Y = ((160 - curve_ratio * getHighTempByPercent(i + 1))) - (((160 - curve_ratio * getHighTempByPercent(i + 2))) - ((160 - curve_ratio * getHighTempByPercent(i)))) / 4;
//3阶贝塞尔曲线
highPath.cubicTo(
highControlPt1X, highControlPt1Y,
highControlPt2X, highControlPt2Y,
startX + mTempWidth, (160 - curve_ratio * getHighTempByPercent(i + 1)));
canvas.drawPath(highPath, mCurvePaint);
//每次绘制后将画笔恢复
highPath.reset();
}
//中间曲线
if (i != 0 && i < dataArray.size() - 2) {
highPath.moveTo(startX, (160 - curve_ratio * getHighTempByPercent(i)));
highControlPt1X = startX + mTempWidth / 4;
highControlPt1Y = ((160 - curve_ratio * getHighTempByPercent(i))) + (((160 - curve_ratio * getHighTempByPercent(i + 1))) - ((160 - curve_ratio * getHighTempByPercent(i - 1)))) / 4;
highControlPt2X = startX + (mTempWidth / 4) * 3;
highControlPt2Y = ((160 - curve_ratio * getHighTempByPercent(i + 1))) - (((160 - curve_ratio * getHighTempByPercent(i + 2))) - ((160 - curve_ratio * getHighTempByPercent(i)))) / 4;
highPath.cubicTo(
highControlPt1X, highControlPt1Y,
highControlPt2X, highControlPt2Y,
startX + mTempWidth, (160 - curve_ratio * getHighTempByPercent(i + 1)));
canvas.drawPath(highPath, mCurvePaint);
highPath.reset();
}
//最后一段曲线
if (i == dataArray.size() - 2) {
highPath.moveTo(startX, (160 - curve_ratio * getHighTempByPercent(i)));
highControlPt1X = startX + mTempWidth / 4;
highControlPt1Y = ((160 - curve_ratio * getHighTempByPercent(i))) + (((160 - curve_ratio * getHighTempByPercent(i + 1))) - ((160 - curve_ratio * getHighTempByPercent(i - 1)))) / 4;
highControlPt2X = startX + (mTempWidth / 4) * 3;
highControlPt2Y = 160 - curve_ratio * getHighTempByPercent(i + 1);
highPath.cubicTo(
highControlPt1X, highControlPt1Y,
highControlPt2X, highControlPt2Y,
startX + mTempWidth, (160 - curve_ratio * getHighTempByPercent(i + 1)));
canvas.drawPath(highPath, mCurvePaint);
highPath.reset();
}

//绘制最低温度,与绘制最高温度类似

//绘制日期
float dayTextWidth = mTextPaint.measureText(dataArray.get(i).getDate() + “日”);
float dayStartX = startX - dayTextWidth / 2;
float dayTextStartY = 40 + getFontAscentHeight(mTextPaint);
drawDayText(canvas, dataArray.get(i).getDate() + “日”, dayStartX, dayTextStartY);

//绘制天气
float typeTextWidth = mTextPaint.measureText(dataArray.get(i).getType());
float typeTextStartX = startX - typeTextWidth / 2;
float typeTextStartY = measureHeight - 40 - getFontDescentHeight(mTextPaint);
canvas.drawText(dataArray.get(i).getT 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》无偿开源 徽信搜索公众号【编程进阶路】 ype(), typeTextStartX, typeTextStartY, mTextPaint);
//每绘制完一天,往后移动mTempWidth距离,绘制下一天
startX = startX + mTempWidth;
}
}
}

其中的一些参数是可以根据需要更改的。

重写dispatchTouchEvent()。当滑动到最左边也就是第一天的时候,应该禁止继续向右继续滑动。滑动到最右边同理。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int dispatchCurrX = (int) ev.getX();
int dispatchCurrY = (int) ev.getY();
switch (ev.getAction()) {

case MotionEvent.ACTION_MOVE:
float deltaX = dispatchCurrX - dispatchTouchX;
float deltaY = dispatchCurrY - dispatchTouchY;
//竖直滑动的父容器拦截事件
if (Math.abs(deltaY) - Math.abs(deltaX) > 0) {
getParent().requestDisallowInterceptTouchEvent(false);
}
//向右滑动,滑动到左边边界,父容器进行拦截
if ((dispatchCurrX - dispatchTouchX) > 0 && mStartX == 0) {
getParent().requestDisallowInterceptTouchEvent(false);
} else if ((dispatchCurrX - dispatchTouchX) < 0 && mStartX == -getMoveLength()) {
//向左滑动,滑动到右边边界,父容器进行拦截
getParent().requestDisallowInterceptTouchEvent(false);
}
break;


}
dispatchTouchX = dispatchCurrX;
dispatchTouchY = dispatchCurrY;
return super.dispatchTouchEvent(ev);
}

重写onTouchEvent()。

@SuppressLint(“ClickableViewAccessibility”)
@Override
public boolean onTouchEvent(MotionEvent event) {
//如果正在执行动画,直接返回
if (isAnimation) {
return true;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = event.getX();
//当点击的时候,判断如果是在fling的效果的时候,就停止快速滑动
if (isFling) {
removeCallbacks(mScrollRunnable);
isFling = false;
}
break;

case MotionEvent.ACTION_MOVE:
float currX = event.getX();
mStartX += currX - lastX;
//计算每次滑动后的mStartX
//向右滑动
if ((currX - lastX) > 0) {
if (mStartX > 0) {
mStartX = 0;
}
} else {//向左滑动
if (-mStartX > getMoveLength()) {
mStartX = -getMoveLength();
}
}
lastX = currX;
break;

case MotionEvent.ACTION_UP:
//1s内的滑动速度
mVelocityTracker.computeCurrentVelocity(1000);
//计算快速滑动的速度,如果是大于某个值,并且数据的长度大于整个屏幕的长度,那么就允许有fling后逐渐停止的效果
if (Math.abs(mVelocityTracker.getXVelocity()) > 100
&& !isFling && measureWidth < dataArray.size() * mTempWidth) {
mScrollRunnable = new ScrollRunnable(mVelocityTracker.getXVelocity() / 5);
this.post(mScrollRunnable);
}
break;

case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}

private class ScrollRunnable implements Runnable {

private float speed;

ScrollRunnable(float speed) {
this.speed = speed;
}

@Override
public void run() {
if (Math.abs(speed) < 60) {
isFling = false;
return;
}
isFling = true;
mStartX += speed / 15;
//速度有一个渐慢的效果
speed = speed / 1.1f;
//向右滑动
if ((speed) > 0) {
if (mStartX > 0) {
mStartX = 0;
}
} else {
//向右滑动
if (-mStartX > getMoveLength()) {
mStartX = -getMoveLength();
}
}
postDelayed(this, 5);
invalidate();
}
}

以上为部分主要代码,自定义View就算是完成了。

###城市搜索
使用单独一个Activity,使用了DataBinding来做搜索编辑框的绑定,RecyclerView用来展示返回的城市列表,选择其中的某一城市后,通过EventBus将城市信息通知MainActivity。新建CityActivity,添加CityViewHolder类,并在其中添加afterTextChanged(Editable s)方法,在onCreate中完成代码和视图的绑定

public class CityActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//绑定代码和视图
ActivityCityBinding activityCityBinding = DataBindingUtil.setContentView(this, R.layout.activity_city);
activityCityBinding.setCityViewHolder(new CityViewHolder());


}

public class CityViewHolder {

public void afterTextChanged(Editable s) {

}
}
}

修改对应的activity_city.xml

<?xml version="1.0" encoding="utf-8"?>

这样,每次修改EditText都会走afterTextChanged()。

接下完成搜索城市的请求。新建CityBean.java,和上面一样,通过GsonFormat自动生成代码,Json数据可以在和风天气的接口文档的数据返回示例中看到。接下来新建CityService.java,添加getCall方法。查看和风天气的接口文档,location和key是必选的,通过@Query注解添加请求URL中的这两个参数。返回类型中的泛型为刚刚完成的CityBean类。

public interface CityService {

@GET(“find”)
Observable getCall(@Query(“location”) String location,
@Query(“key”) String key);

}

实现afterTextChanged(),和上面天气数据请求基本一致。

public void afterTextChanged(Editable s) {

//如果输入为空,直接返回
if (s.toString().equals(“”)) {
return;
}

Retrofit retrofit = new Retrofit.Builder()
.baseUrl(“https://search.heweather.net/”)
.addConverterFactory(GsonConverterFactory.create())//使用Gson
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//使用RxJava
.build();

CityService cityService = retrofit.create(CityService.class);
cityService.getCall(s.toString(), KEY)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {

}

@Override
public void onNext(CityBean cityBean) {
//对获取到的数据进行处理
}

@Override
public void onError(Throwable e) {

}

@Override
public void onComplete() {

}
});

}

新建CitysAdapter.java,继承自RecyclerView.Adapter,作为RecyclerView的Adapter。

public class CitysAdapter extends RecyclerView.Adapter<CitysAdapter.ViewHolder>{

private ArrayList citys;

private Context mContext;

private OnItemClick mOnItemClick;

CitysAdapter(ArrayList citys, Context parentContext) {
this.citys = citys;
mContext = parentContext;
}

void setOnItemClick(OnItemClick onItemClick) {
mOnItemClick = onItemClick;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值