Android简易天气App

  • Post author:
  • Post category:其他




前言

本打算是写一个贝塞尔曲线的demo,想了一下哪种场景可以直观的表现出贝塞尔曲线,想到天气预报中的那些24小时和未来几日天气变化正好适用。接着开始构思,开始是打算把数据写死的,然后想了想既然是模拟天气预报,为了真实一点,干脆就从网络获取吧,就找了个天气接口,一看接口还有好多其他的数据,干脆都用了吧。然后又想了一想,既然已经做成天气预报了,切换城市是必须要有的吧。最后就变成了一个简易的天气App。

先上效果图,上面和下面两部分是显示的一些基本数据,中间那个可滑动的未来15日天气就是一开始打算写的贝塞尔曲线部分。选择左上角的城市,会跳转到搜索界面,可以搜索想要查看城市的天气状况。

在这里插入图片描述

在这里插入图片描述



使用到的知识

网络请求:Retrofit + RxJava

网络数据解析:Gson

天气图标获取:Glide

主界面和搜索界面的消息传递:EventBus

天气变化曲线:自定义View

数据缓存:SharedPreference

视图绑定:ButterKnife

搜索编辑框:DataBinding

搜索结果列表:RecyclerView



准备工作

一共使用了3个接口,一个用来请求天气数据,一个用来请求天气类型的图标,一个用来搜索城市。请求天气的接口为http://t.weather.sojson.com/api/weather/city/city_code,接口及返回Json数据示例https://www.sojson.com/blog/305.html有说明,其中的city_code通过搜索城市的接口得到,使用的是和风天气的接口。返回数据中的天气类型用来请求天气类型图标,也是和风天气的接口。和风天气的官网上有说明。请求天气接口返回的数据是Json格式的,这就需要做Json的解析,我这里用的是Gson。使用Gson是需要与数据相对应的Bean类的,Android Studio中正好有一个插件叫做GsonFormat,可以自动生成对应的Bean类。选择File Settings/Plugins/MarketPlace,搜索GsonFormat,安装就可以了,我这个是已经安装过了。
在这里插入图片描述

在app的build.gradle中添加要使用到的依赖。直接都添加好了,已经将后续所有用到的都添加了好了。顺便添加compileOptions和dataBinding两段代码。

android {
    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    dataBinding {
        enabled = true
    }
}

dependencies {
    ...
    implementation 'com.android.support:recyclerview-v7:28.0.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.2.0'
    implementation 'com.squareup.retrofit2:retrofit:2.2.0'
    implementation 'com.jakewharton:butterknife:9.0.0'
    implementation 'com.squareup.okhttp3:okhttp:3.10.0'
    implementation 'org.greenrobot:eventbus:3.0.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:9.0.0'
    ...
}

在project的build.gradle中添加

dependencies {
    ...
    classpath 'com.jakewharton:butterknife-gradle-plugin:9.0.0'
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    
    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
}



天气数据网络请求

分为两块,一块是天气和图标的请求,使用RxJava的flatMap操作符将两者连在一起:首先请求天气数据,得到数据后使用flatMap操作符,取出数据中的天气类型进行第二次网络请求,最后在主线程中处理数据。第二块是搜索城市返回城市代码,一个简单的Retrofit + RxJava就可实现。

在https://www.sojson.com/blog/305.html中,有天气接口成功返回值的Json代码示例,去掉中间的注释,只要Json数据。因为使用的Gson,首先准备天气数据的Bean类。新建WeatherBean.java,将光标放到类的括号中,右键选择Generate,选择GsonFormat,把Json代码粘进去,ok,会在类中自动生成对应的代码,看一眼与数据是对应的。

在这里插入图片描述

在这里插入图片描述

写Retrofit中的service接口。新建WeatherService.java,类型为interface。添加getCall方法,GET请求,因为接口为http://t.weather.sojson.com/api/weather/city/+city_code。每次请求改变city_code即可,通过@Path注解实现。类型Observable< WeatherBean >,其中Observable是因为用了RxJava,WeatherBean就是前面生成的Bean类。

public interface WeatherService {
	//每次请求city_code可变
    @GET("{city_code}")
    Observable<WeatherBean> getCall(@Path("city_code") String code);
}

在MainActivity中使用Retrofit,添加requestWeather(String cityId)方法。

private static CompositeDisposable compositeDisposable = new CompositeDisposable();
@SuppressLint("CheckResult")
private void requestWeather(String cityId) {
    bitmaps.clear();
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://t.weather.sojson.com/api/weather/city/")
            .addConverterFactory(GsonConverterFactory.create())//使用Gson
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//使用RxJava
            .build();
    final WeatherService weatherService = retrofit.create(WeatherService.class);

	//第一次网络请求,获取天气数据
    weatherService.getCall(cityId)
            .subscribeOn(Schedulers.io())
            .flatMap((Function<WeatherBean, Observable<WeatherBean>>) weatherBean -> {
                ...
                for (WeatherBean.DataBean.ForecastBean forecastBean : weatherBean.getData().getForecast()) {
                    ...
                    
                    //循环取出每一天的天气类型,添加url
                    String url = "https://cdn.heweather.com/cond_icon/" + preferences.getString(forecastBean.getType(), "未知");
		
                    //使用Glide通过天气类型进行第二次网络请求,获取类型对应天气图标bitmap
                    FutureTarget<Bitmap> target = Glide.with(getApplicationContext())
                            .asBitmap()
                            .load(url)
                            .submit();
                    final Bitmap bitmap = target.get();
                    dataArrayList.add(new MyCurveView.WeatherData(low, high, date, type, bitmap));
                }
                ...
                
                //将请求返回的天气数据继续传至主线程的Observer观察者
                return Observable.fromArray(weatherBean);
            })
            .observeOn(AndroidSchedulers.mainThread())//切换至主线程
            .subscribe(new Observer<WeatherBean>() {

                @Override
                public void onSubscribe(Disposable d) {
					compositeDisposable.add(d);
                }

                @SuppressLint("SetTextI18n")
                @Override
                public void onNext(WeatherBean weatherBean) {
                	//在主线程中处理得到的数据
                }

                @Override
                public void onError(Throwable e) {
	                  
                }

                @Override
                public void onComplete() {
                    
                }
            });
}



自定义View

布局中间展示未来15天天气,数据有日期、最高温度、最低温度、类型、类型图标,其中温度连成两条曲线,整体支持滑动。

我是这样设计的,温度曲线初始为两条直线,为这15天的平均值,然后开始变化,变到对应的值,从而形成曲线效果。

新建MyCurveView.java,继承自View。添加WeatherData内部类,添加对应的属性及get、set方法。

static class WeatherData {
    private float lowTemp;
    private float highTemp;
    private int date;
    private String type;
    private Bitmap typeBitmap;
        
    WeatherData(float lowTemp, float highTemp, int date, String type, Bitmap typeBitmap) {
        this.lowTemp = lowTemp;
        this.highTemp = highTemp;
        this.date = date;
        this.type = type;
        this.typeBitmap = typeBitmap;
    }

   ...
}

添加setProgress()方法,在网络请求完毕后,调用该方法更新数据和UI。首先调用arrayList保存网络数据,然后在动画中不断更新视图。

public void setProgress(int averageHigh, int averageLow, final int low, int top, ArrayList<WeatherData> innerData) {
    arrayList(innerData, top, low, averageHigh, averageLow);
    ValueAnimator animatorHigh = ValueAnimator.ofInt(0, top);
    animatorHigh.setDuration(1000);
    animatorHigh.setInterpolator(new AccelerateInterpolator());
    animatorHigh.addUpdateListener(valueAnimator -> {
        mHighPercent = (int)valueAnimator.getAnimatedValue();
        invalidate();
    });

    ValueAnimator animatorLow = ValueAnimator.ofInt(0, low);
    animatorLow.setDuration(1000);
    animatorLow.setInterpolator(new AccelerateInterpolator());
    animatorLow.addUpdateListener(valueAnimator -> {
            mLowPercent = (int)valueAnimator.getAnimatedValue();
        });

    AnimatorSet set = new AnimatorSet();
    //两个动画同时进行
    set.playTogether(animatorHigh, animatorLow);
    //监听动画
    set.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
        	//在做动画的时间内,通过该标志位禁止触摸动作
            isAnimation = true;
        }

        @Override
        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<WeatherData> 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).getType(), 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"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="cityPresenter"
            type="com.sk.simpleweather.CityActivity.CityViewHolder" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="20dp"
        android:layout_marginStart="30dp"
        android:layout_marginEnd="30dp"
        android:orientation="vertical">

        <EditText
            android:id="@+id/search_edittext"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="5dp"
            android:background="@drawable/et_bgd"
            android:focusable="true"
            android:textSize="16sp"
            android:maxLines="1"
            android:singleLine="true"
            android:hint="输入城市"
            android:textColorHint="#40000000"
            android:afterTextChanged="@{cityPresenter.afterTextChanged}"/>

        ...

    </LinearLayout>
</layout>

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

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

public interface CityService {

    @GET("find")
    Observable<CityBean> 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<CityBean>() {
                @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<String> citys;

    private Context mContext;

    private OnItemClick mOnItemClick;

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

    void setOnItemClick(OnItemClick onItemClick) {
        mOnItemClick = onItemClick;
    }
	
    interface OnItemClick {
    	//回调函数,在CityActivity中实现该方法。
        void onClick(String city, int position);
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

        private LinearLayout layout;

        private TextView textView;

        ViewHolder(@NonNull View itemView) {
            super(itemView);
            layout = itemView.findViewById(R.id.city_layout);
            textView = itemView.findViewById(R.id.city);
        }
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        if (mContext == null) {
            mContext = viewGroup.getContext();
        }
        View view = LayoutInflater.from(mContext).inflate(R.layout.city_item, viewGroup, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
        viewHolder.textView.setText(citys.get(position));
        viewHolder.layout.setOnClickListener(v -> mOnItemClick.onClick(citys.get(position), position));
    }

    @Override
    public int getItemCount() {
        return citys.size();
    }

}

添加CityMessageEvent.java,供EventBus使用。

public class CityMessageEvent {

    private String name;

    private String cityId;

	//name和cityId的set、get方法
    ...

}

在CityActivity中实现上面的onClick()方法。

private CityMessageEvent messageEvent;

@Override
public void onClick(String city, int position) {
    if (mCityBean != null) {
        messageEvent.setName(city);
        messageEvent.setCityId(mCityBean.getHeWeather6().get(0).getBasic().get(position).getCid().replace("CN", ""));
        //通过EventBus发射event
        EventBus.getDefault().postSticky(messageEvent);
    }
    finish();
}

修改MainActivity,添加onMessageEvent()方法,用来接收EventBus发出的event。接收到数据后,再次请求网络数据。

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onMessageEvent(CityMessageEvent messageEvent) {
    requestWeather(messageEvent.getCityId());
}



内存泄漏

回到MainActivity,重写onDestroy。

@Override
protected void onDestroy() {
    Log.d(TAG, "onDestroy: ");
    super.onDestroy();
    myBarCharView.finish();
    if (!compositeDisposable.isDisposed()) {
    	//通过clear解除Rxjava中的订阅
        compositeDisposable.clear();
    }
    if (EventBus.getDefault().isRegistered(this)) {
    	//取消EventBus的注册
        EventBus.getDefault().unregister(this);
    }
}

MyCurveView中添加一个finish方法。因为可能在动画还未完成的情况下界面就销毁了,会导致内存泄漏,所以在Activity onDestroy的时候取消动画。

public void finish() {
    if (set != null) {
        set.cancel();
        set = null;
    }
}

最后附上源码地址,有什么疑问及意见欢迎大家指出,后续有时间会对代码进行改进。要运行的话,麻烦在和风天气官网注册一下,自己新建一个工程,使用自己的Key,谢谢~~

项目地址:

GitHub


转载请附上本文链接,谢谢~~~



版权声明:本文为songkai0825原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。