个人博客CoorChice,https://chenbingx.github.io/ ,最新文章将会首发CoorChice的博客,欢迎探索哦 !
同时,搜索CoorChice
关注我的微信公众号,同期文章也将会优先推送到公众号中,以提醒您有新鲜文章出炉。亦可在文章末尾扫描二维码关注。
本系列文章列表
截止上一篇,我们已经完成了欢迎页,并且成功的请求到了定位地点的天气数据,并且缓存起来。本篇我们将开始构建我们的核心主页,它用于展示天气数据。
主页需求
- 能够展示未来几天的天气;
- 能够展示当日的详细天气情况;
- 能够展示多地天气。
暂时先这几条,我们后面还可以再添加。现在我们开始着手实现这几个需求。
实现需求
根据以上需求,我们不但需要展示一天的详细数据和未来几天的天气数据,还要具备展示多地天气数据。所以大概可以设计一下,使用列表控件 来展示一个地区的详细数据,使用ViewPager 来展示不同地区的详细数据。话不多说,来看看这样的效果图。
主要xml文件
这个页面很简单,它由Activity和Fragment构成。Fragments被放到了ViewPager中。
下面来看看各个布局文件。
Activity.xml
//用来容纳Fragment
//后期会在底部添加操作栏,所以先留出位置。
Fragment.xml
//用来装未来天气数据和当日详细信息
Activity
这个Activity的结构和之前的欢迎页差不多。
View模块
首先创建MainActivity的接口。
public interface MainActivityView extends MvpView {
}
继承接口,创建Activity。
public class MainActivity extends BaseActivity implements MainActivityView {
@BindView(R.id.pager_container)
ViewPager pagerContainer;
private MainActivityPresenterApi presenter; //依赖Presenter的抽象
private List fragments = new ArrayList<>(); //这个数组用来装每个地区的天气页面
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter = new MainActivityPresenter(this);
ButterKnife.bind(this);
initData();
initView();
addListener();
}
@Override
protected void initData() {
fragments.add(WeatherDetailFragment.newInstance(null)); //创建Fragment
//这里之所以穿Null是因为我在后面处理时,把默认天气数据设置为定位地区的
}
@Override
protected void initView() {
setWindowProperties();
//设置ViewPager的适配器
pagerContainer.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
});
pagerContainer.setPageTransformer(false, new ZoomOutPageTransformer());
}
private void setWindowProperties() {
// 实现透明状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
getWindow().setFormat(PixelFormat.TRANSPARENT);
}
@Override
protected void addListener() {
}
@Override
protected BasePresenter getPresenter() {
return presenter;
}
}
Presenter模块
由于目前Activity本身并不处理什么任务,所以Presenter中还没内容,但是我们任然要按照架构写,因为后面可能会添加东西进去。
//Presenter接口
public interface MainActivityPresenterApi extends BasePresenter {
}
//Presenter实现,因为暂时不处理事物,所以先不写Model了
public class MainActivityPresenter implements MainActivityPresenterApi {
private MainActivityView view;
public MainActivityPresenter(MainActivityView view) {
this.view = view;
}
@Override
public void destroy() {
view = null;
}
}
Fragment
上面代码中可以看到,我们已经创建了一个Fragment,下面就来看看这个Fragment是什么样的。
Fragment同样需要按照MVP模式来,首先需要改造一下BaseFragment。
public abstract class BaseFragment extends Fragment {
abstract protected void initData();
abstract protected void initView();
abstract protected void addListener();
/**
* 创建Presenter后必须重写这个方法,将其作为返回值
*/
abstract protected BasePresenter getPresenter();
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.setClickable(true); //这个操作是为了防止Fragment出现点透Bug
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (getPresenter() != null){
getPresenter().destroy(); //销毁Presenter,避免Activity对象因被Presenter持有而不能被销毁
}
}
}
View模块
Fragment的接口。
public interface WeatherDetailFragmentView extends MvpView {
void onWeatherDataUpdate(WeatherData data); //通知View更新数据,这个方法供Presenter调用
}
实现Fragment,包含了交互操作。
public class WeatherDetailFragment extends BaseFragment implements WeatherDetailFragmentView {
public static final String CITY_NAME = "city_name";
@BindView(R.id.header)
ViewGroup header;
@BindView(R.id.city_name)
TextView cityName;
@BindView(R.id.weather_info)
TextView weatherInfo;
@BindView(R.id.air_quality)
TextView airQuality;
@BindView(R.id.temperature)
TextView temperature;
@BindView(R.id.recyclerView)
RecyclerView recyclerView;
private WeatherDetailFragmentPresenterApi presenter; //依赖Presenter的抽象
private WeatherData data;
private FutureWeathersAdapter adapter;
private int totalDy;
private int alphaReferenceValue;
public static BaseFragment newInstance(String cityName) { //用静态方法创建Fragment
WeatherDetailFragment instance = new WeatherDetailFragment();
Bundle args = new Bundle();
args.putString(CITY_NAME, cityName); //储存需要显示的地区名字在Fragment的Argument中
instance.setArguments(args);
return instance;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_weather_detail, container, false);
ButterKnife.bind(this, rootView);
presenter = new WeatherDetailFragmentPresenter(this); //创建Fragment的Presenter
initData();
initView();
addListener();
return rootView;
}
@Override
protected void initData() {
String cityName = getArguments().getString(CITY_NAME);
presenter.getWeatherData(cityName); //请求天气数据
}
@Override
protected void initView() {
//看上面效果图,我们只需要使用LinearLayoutManager就可以了
recyclerView.setLayoutManager(
new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
recyclerView.setItemAnimator(new DefaultItemAnimator());
}
@Override
protected void addListener() {
listenRecyclerView();
temperature.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
computeAlphaReferenceValue();
temperature.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
private void computeAlphaReferenceValue() {
//下面是在计算设置温度TextView的Alpha值的参考值,效果是在RecyclerView的第一个Item滑动到温度TextView的1/8时,温度TextView刚好完全透明
int temperatureY = temperature.getBottom();
int temperatureHeight = temperature.getMeasuredHeight();
int recyclerViewPaddingTop = recyclerView.getPaddingTop();
alphaReferenceValue = recyclerViewPaddingTop - temperatureY + temperatureHeight / 8;
}
private void listenRecyclerView() {
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
totalDy -= dy; //记录RecyclerView滑动的距离,上滑为负,下滑为正
float translationY = (float) (totalDy * 0.3); //取滑动距离的30%来移动上半部分View才能造成层次感
header.setTranslationY(translationY); //设置整个上半部分View的TranslationY,实现图中的滑动效果。
//translationY初始值为0,负值向上移动,正值向下移动,与Android坐标系的方向是一致的
float alpha = 1 - (float) (Math.abs(totalDy * 0.3)) / alphaReferenceValue;
temperature.setAlpha(alpha); //设置Alpha值
super.onScrolled(recyclerView, dx, dy);
}
});
}
@Override
protected BasePresenter getPresenter() {
return presenter;
}
@Override
public void onWeatherDataUpdate(WeatherData data) {
if (data != null) {
updateView(data.getData());
}
}
private void updateView(WeatherData.Data data) {
setHeaderView(data);
setRecyclerView(data);
}
private void setHeaderView(WeatherData.Data data) {
String cityName = data.getRealtime().getCity_name();
this.cityName.setText(cityName);
String weatherInfo = data.getRealtime().getWeather().getInfo();
this.weatherInfo.setText(weatherInfo);
String airQualityStrFormat = getString(R.string.air_quality);
String airQuality = String.format(airQualityStrFormat, data.getPm25().getPm25().getQuality());
this.airQuality.setText(airQuality);
String temperatureFormat = getString(R.string.temperature);
String temperature =
String.format(temperatureFormat, data.getRealtime().getWeather().getTemperature());
this.temperature.setText(temperature);
}
private void setRecyclerView(WeatherData.Data data) {
if (data == null) {
return;
}
if (adapter == null) {
recyclerView.setAdapter(new FutureWeathersAdapter(getActivity(), data)); //在这里才创建RecyclerView的Adapter
} else {
adapter.updateData(data);
}
}
}
由于篇幅问题,本篇先讲到这。后面的文章中我们在一起完成Fragment的Presenter、Model,以及编写RecyclerView的Adapter。
本项目已上传至GitHub,详细源码请到GitHub查看。
项目地址GitHub
截止上一篇,我们已经完成了欢迎页,并且成功的请求到了定位地点的天气数据,并且缓存起来。本篇我们将开始构建我们的核心主页,它用于展示天气数据。
主页需求
- 能够展示未来几天的天气;
- 能够展示当日的详细天气情况;
- 能够展示多地天气。
暂时先这几条,我们后面还可以再添加。现在我们开始着手实现这几个需求。
实现需求
根据以上需求,我们不但需要展示一天的详细数据和未来几天的天气数据,还要具备展示多地天气数据。所以大概可以设计一下,使用列表控件 来展示一个地区的详细数据,使用ViewPager 来展示不同地区的详细数据。话不多说,来看看这样的效果图。
主要xml文件
这个页面很简单,它由Activity和Fragment构成。Fragments被放到了ViewPager中。
下面来看看各个布局文件。
Activity.xml
//用来容纳Fragment
//后期会在底部添加操作栏,所以先留出位置。
Fragment.xml
//用来装未来天气数据和当日详细信息
Activity
这个Activity的结构和之前的欢迎页差不多。
View模块
首先创建MainActivity的接口。
public interface MainActivityView extends MvpView {
}
继承接口,创建Activity。
public class MainActivity extends BaseActivity implements MainActivityView {
@BindView(R.id.pager_container)
ViewPager pagerContainer;
private MainActivityPresenterApi presenter; //依赖Presenter的抽象
private List fragments = new ArrayList<>(); //这个数组用来装每个地区的天气页面
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter = new MainActivityPresenter(this);
ButterKnife.bind(this);
initData();
initView();
addListener();
}
@Override
protected void initData() {
fragments.add(WeatherDetailFragment.newInstance(null)); //创建Fragment
//这里之所以穿Null是因为我在后面处理时,把默认天气数据设置为定位地区的
}
@Override
protected void initView() {
setWindowProperties();
//设置ViewPager的适配器
pagerContainer.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
});
pagerContainer.setPageTransformer(false, new ZoomOutPageTransformer());
}
private void setWindowProperties() {
// 实现透明状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
getWindow().setFormat(PixelFormat.TRANSPARENT);
}
@Override
protected void addListener() {
}
@Override
protected BasePresenter getPresenter() {
return presenter;
}
}
Presenter模块
由于目前Activity本身并不处理什么任务,所以Presenter中还没内容,但是我们任然要按照架构写,因为后面可能会添加东西进去。
//Presenter接口
public interface MainActivityPresenterApi extends BasePresenter {
}
//Presenter实现,因为暂时不处理事物,所以先不写Model了
public class MainActivityPresenter implements MainActivityPresenterApi {
private MainActivityView view;
public MainActivityPresenter(MainActivityView view) {
this.view = view;
}
@Override
public void destroy() {
view = null;
}
}
Fragment
上面代码中可以看到,我们已经创建了一个Fragment,下面就来看看这个Fragment是什么样的。
Fragment同样需要按照MVP模式来,首先需要改造一下BaseFragment。
public abstract class BaseFragment extends Fragment {
abstract protected void initData();
abstract protected void initView();
abstract protected void addListener();
/**
* 创建Presenter后必须重写这个方法,将其作为返回值
*/
abstract protected BasePresenter getPresenter();
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.setClickable(true); //这个操作是为了防止Fragment出现点透Bug
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (getPresenter() != null){
getPresenter().destroy(); //销毁Presenter,避免Activity对象因被Presenter持有而不能被销毁
}
}
}
View模块
Fragment的接口。
public interface WeatherDetailFragmentView extends MvpView {
void onWeatherDataUpdate(WeatherData data); //通知View更新数据,这个方法供Presenter调用
}
实现Fragment,包含了交互操作。
public class WeatherDetailFragment extends BaseFragment implements WeatherDetailFragmentView {
public static final String CITY_NAME = "city_name";
@BindView(R.id.header)
ViewGroup header;
@BindView(R.id.city_name)
TextView cityName;
@BindView(R.id.weather_info)
TextView weatherInfo;
@BindView(R.id.air_quality)
TextView airQuality;
@BindView(R.id.temperature)
TextView temperature;
@BindView(R.id.recyclerView)
RecyclerView recyclerView;
private WeatherDetailFragmentPresenterApi presenter; //依赖Presenter的抽象
private WeatherData data;
private FutureWeathersAdapter adapter;
private int totalDy;
private int alphaReferenceValue;
public static BaseFragment newInstance(String cityName) { //用静态方法创建Fragment
WeatherDetailFragment instance = new WeatherDetailFragment();
Bundle args = new Bundle();
args.putString(CITY_NAME, cityName); //储存需要显示的地区名字在Fragment的Argument中
instance.setArguments(args);
return instance;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_weather_detail, container, false);
ButterKnife.bind(this, rootView);
presenter = new WeatherDetailFragmentPresenter(this); //创建Fragment的Presenter
initData();
initView();
addListener();
return rootView;
}
@Override
protected void initData() {
String cityName = getArguments().getString(CITY_NAME);
presenter.getWeatherData(cityName); //请求天气数据
}
@Override
protected void initView() {
//看上面效果图,我们只需要使用LinearLayoutManager就可以了
recyclerView.setLayoutManager(
new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
recyclerView.setItemAnimator(new DefaultItemAnimator());
}
@Override
protected void addListener() {
listenRecyclerView();
temperature.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
computeAlphaReferenceValue();
temperature.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
private void computeAlphaReferenceValue() {
//下面是在计算设置温度TextView的Alpha值的参考值,效果是在RecyclerView的第一个Item滑动到温度TextView的1/8时,温度TextView刚好完全透明
int temperatureY = temperature.getBottom();
int temperatureHeight = temperature.getMeasuredHeight();
int recyclerViewPaddingTop = recyclerView.getPaddingTop();
alphaReferenceValue = recyclerViewPaddingTop - temperatureY + temperatureHeight / 8;
}
private void listenRecyclerView() {
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
totalDy -= dy; //记录RecyclerView滑动的距离,上滑为负,下滑为正
float translationY = (float) (totalDy * 0.3); //取滑动距离的30%来移动上半部分View才能造成层次感
header.setTranslationY(translationY); //设置整个上半部分View的TranslationY,实现图中的滑动效果。
//translationY初始值为0,负值向上移动,正值向下移动,与Android坐标系的方向是一致的
float alpha = 1 - (float) (Math.abs(totalDy * 0.3)) / alphaReferenceValue;
temperature.setAlpha(alpha); //设置Alpha值
super.onScrolled(recyclerView, dx, dy);
}
});
}
@Override
protected BasePresenter getPresenter() {
return presenter;
}
@Override
public void onWeatherDataUpdate(WeatherData data) {
if (data != null) {
updateView(data.getData());
}
}
private void updateView(WeatherData.Data data) {
setHeaderView(data);
setRecyclerView(data);
}
private void setHeaderView(WeatherData.Data data) {
String cityName = data.getRealtime().getCity_name();
this.cityName.setText(cityName);
String weatherInfo = data.getRealtime().getWeather().getInfo();
this.weatherInfo.setText(weatherInfo);
String airQualityStrFormat = getString(R.string.air_quality);
String airQuality = String.format(airQualityStrFormat, data.getPm25().getPm25().getQuality());
this.airQuality.setText(airQuality);
String temperatureFormat = getString(R.string.temperature);
String temperature =
String.format(temperatureFormat, data.getRealtime().getWeather().getTemperature());
this.temperature.setText(temperature);
}
private void setRecyclerView(WeatherData.Data data) {
if (data == null) {
return;
}
if (adapter == null) {
recyclerView.setAdapter(new FutureWeathersAdapter(getActivity(), data)); //在这里才创建RecyclerView的Adapter
} else {
adapter.updateData(data);
}
}
}
由于篇幅问题,本篇先讲到这。后面的文章中我们在一起完成Fragment的Presenter、Model,以及编写RecyclerView的Adapter。
本项目已上传至GitHub,详细源码请到GitHub查看。