【比你想的简单很多!从0开始完成一款App】8.构建主页(1)

个人博客CoorChice,https://chenbingx.github.io/ ,最新文章将会首发CoorChice的博客,欢迎探索哦 !
同时,搜索CoorChice关注我的微信公众号,同期文章也将会优先推送到公众号中,以提醒您有新鲜文章出炉。亦可在文章末尾扫描二维码关注。

【比你想的简单很多!从0开始完成一款App】8.构建主页(1)_第1张图片
封面.jpg

本系列文章列表

截止上一篇,我们已经完成了欢迎页,并且成功的请求到了定位地点的天气数据,并且缓存起来。本篇我们将开始构建我们的核心主页,它用于展示天气数据。

主页需求

  • 能够展示未来几天的天气;
  • 能够展示当日的详细天气情况;
  • 能够展示多地天气。

暂时先这几条,我们后面还可以再添加。现在我们开始着手实现这几个需求。

实现需求

根据以上需求,我们不但需要展示一天的详细数据和未来几天的天气数据,还要具备展示多地天气数据。所以大概可以设计一下,使用列表控件 来展示一个地区的详细数据,使用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查看。

项目地址GitHub

【比你想的简单很多!从0开始完成一款App】8.构建主页(1)_第2张图片
CoorChice的公众号

你可能感兴趣的:(【比你想的简单很多!从0开始完成一款App】8.构建主页(1))