博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android从零撸美团(四) - 美团首页布局解析及实现 - Banner+自定义View+SmartRefreshLayout下拉刷新上拉加载更多...
阅读量:6083 次
发布时间:2019-06-20

本文共 12998 字,大约阅读时间需要 43 分钟。

这是【从零撸美团】系列文章第四篇。 项目地址:

仿美团开源项目整体架构和首页其实早就完成了,前段时间家里各种事情搞得心力交瘁,停更了一段时间。甚至一度动摇继续这个项目的决心,因为最近在学前端,在技术的深度和广度之间一直纠结摇摆不定。一个声音是继续完成这个项目,把安卓玩的更深入一些;另一个声音是赶紧学前端吧,抓紧拓展技术栈,不要在这个项目上浪费太多精力。

思来想去还是继续完成项目吧,自己开的项目跪着也要走完 〒▽〒

最后确定了继续写项目和学前端同时进行的战略方针~

老规矩,先上图,再 分析原理 --> 准备材料 --> 具体实现 三步走一步步的搞定。

一、分析

相比于普通的应用,美团、去哪儿这样的平台性 App 的首页还是相当复杂的,简直想把全世界都包进去~

刚开始看可能觉得眼花缭乱,但仔细观察,可以把它抽象成六个模块:

  1. 最上面的轮播广告条,里面包含若干个广告图片自动无限轮播。暂时称之为 Banner(注意这几个模块起的英文名对应着代码中的模块名)。
  2. 轮播条下面的美食、电影/演出、酒店住宿、休闲娱乐、外卖等五个大模块入口,暂时称之为大模块 BigModule。
  3. 再往下类似 GridView 的两排小图标,KTV、周边游……暂时称之为小模块 SmallModule。
  4. 小模块下面四张广告图片,乍一看是没有规则的瀑布布局,其实是互相对齐的简单规则布局。暂时称之为 HomeAdsView。
  5. 最后就是列表 RecyclerView 了,显示附近团购信息。
  6. 还有一个不太明显的,上拉刷新下拉加载更多,也算一个模块吧。

抽丝剥茧后就是这六个模块啦,是不是一下清爽很多?

实现思路

轮播条选用了第三方的库:, 有 5.2k 颗 star,非常优秀的库。

大模块 BigModule 采用代码中动态添加 View 的方式实现,好处在于能快速响应变化,假如需求变成一行放4个图标,只需要在 java 文件中改一句代码就好,不用修改资源文件。

两行小模块 SmallModule 是 RecyclerView 实现的 GridView。

四张广告图片 HomeAdsView 是封装的自定义 View,高度封装优点是完全解耦,简化了主页的布局,使用配置简单,后期维护方便。

最下面的列表用的是 RecyclerView, 作为辅助。

下拉刷新组件用的是

二、准备

主页中用到了三个框架,在 app/build.gradle 下添加如下依赖:

//Bannerimplementation "com.youth.banner:banner:1.4.10"//BaseRecyclerViewAdapterHelperimplementation "com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30"//SmartRefreshLayoutimplementation "com.scwang.smartrefresh:SmartRefreshLayout:1.0.4"复制代码

注:AndroidStudio 3.0 以上用 implementation,3.0以下用 compile。 项目中还用到了很多其他库,如 Dagger、RxJava、ButterKnife、Glide 等,就不一一贴出来了,具体的使用方式请自行查阅资料或看本项目源码

三、实现

项目采用 MVP 架构,主页代码在 app/home 目录下的 HomeFragmentHomeFragmentPresenter 中。

布局文件是 fragment_home.xml,布局代码如下:

复制代码

布局解析

最外层用 LinearLayout 包裹,接下来是下拉刷新组件 SmartRefreshLayout,因为要实现整个主页的刷新。然后是滚动组件 ScrollView,因为要整体滑动。由于 ScrollView 里只能包含一个子 View,所以在里面又包了层 LinearLayout 。接下来就是五个分模块的具体布局啦。

1. Banner轮播条

添加Banner依赖后,在布局文件中添加 Banner布局,并设置控件高度、图片裁剪模式、滚动时间等参数,然后在 HomeFragment 中初始化:

public void initBanner() {        //设置banner的各种属性        banner.setBannerStyle(BannerConfig.CIRCLE_INDICATOR)                .setImageLoader(new GlideImageLoader())                .setImages(presenter.getBannerImages()) //从Presenter中取出图片资源                .setBannerAnimation(Transformer.Default)                .isAutoPlay(true)                .setDelayTime(3000)                .setIndicatorGravity(BannerConfig.CENTER)                .start();    }复制代码

HomeFragmentPresenter

/**     * 获取Banner的图片资源     *     * @return     */    @Override    public List
getBannerImages() { List
mBannerImages = new ArrayList<>(); mBannerImages.add(R.mipmap.banner1); mBannerImages.add(R.mipmap.banner2); mBannerImages.add(R.mipmap.banner3); mBannerImages.add(R.mipmap.banner4); mBannerImages.add(R.mipmap.banner5); mBannerImages.add(R.mipmap.banner6); return mBannerImages; }复制代码

另外如果想增加体验的话,可以在生命周期的 onStart 方法中开启自动播放,在 onStop 方法中关闭自动播放。

@Override    public void onStart() {        super.onStart();        //增加banner的体验        banner.startAutoPlay();    }    @Override    public void onStop() {        super.onStop();        //增加banner的体验        banner.stopAutoPlay();    }复制代码

Banner 的官方文档中有详细使用方法。

2. 大模块 BigModule 实现

在主页布局中用一个 LinearLayout 作为占位,并确定这个模块的位置。具体的内容在代码中动态添加,方便后期维护修改。 因为做了高度的封装,所以代码多些,但用起来很方便。 先上代码吧:

HomeFragment 是 View 层,按 MVP 分层思想,不应包含具体的逻辑,所以只向外暴露一个共有方法,用于添加自定义 View IconTitleView 到 占位的 LinearLayout

/**     * 往根布局上添加View     */    @Override    public void addViewToBigModule(IconTitleView iconTitleView) {        llBigModule.addView(iconTitleView);    }复制代码

具体的添加逻辑在 HomeFragmentPresenter 中:

//大模块的图片数组    private static final int[] bigModuleDrawables = {            R.mipmap.homepage_icon_light_food_b,            R.mipmap.homepage_icon_light_movie_b,            R.mipmap.homepage_icon_light_hotel_b,            R.mipmap.homepage_icon_light_amusement_b,            R.mipmap.homepage_icon_light_takeout_b,    };    //大模块的标题数组    private static final String[] bigMudoleTitles = {            "美食", "电影/演出", "酒店住宿", "休闲娱乐", "外卖"    };    /**     * 初始化banner下面的5个大模块     */    private void initBigModule() {        for (int i = 0; i < 5; i++) {            IconTitleView iconTitleView = IconTitleView.newInstance(mContext, bigModuleDrawables[i], bigMudoleTitles[i]);            // 设置宽高和权重weight,使每个View占用相同的宽度            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(                    LinearLayout.LayoutParams.WRAP_CONTENT,                    LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f);            iconTitleView.setLayoutParams(lp);            // 往根布局上添加View            mFragment.addViewToBigModule(iconTitleView);            //给View添加点击事件            int finalI = i;            iconTitleView.setOnClickListener((view) -> {                Logger.d(bigMudoleTitles[finalI]);                ToastUtils.show(bigMudoleTitles[finalI]);            });        }    }复制代码

图片和对应的文字都是写好的,分别放在 bigModuleDrawablesbigMudoleTitles 数组中。 这个模块放了五个图标,所以用了 for 循环五次,每次按下标取出上面两个数组中存入的图片和文字资源,通过

IconTitleView iconTitleView = IconTitleView.newInstance(mContext, bigModuleDrawables[i], bigMudoleTitles[i]);复制代码

实例化一个 IconTitleView 对象,并添加到 LinearLayout上

// 往根布局上添加ViewmFragment.addViewToBigModule(iconTitleView);复制代码

注意这几行代码:

// 设置宽高和权重weight,使每个View占用相同的宽度LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(        LinearLayout.LayoutParams.WRAP_CONTENT,        LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f);iconTitleView.setLayoutParams(lp);复制代码

一定要给每个 iconTitleView 设置权重,这样才会让5个图标占用相同的宽度。

自定义 View IconTitleView 的实现:

package com.cachecats.meituan.widget;import android.content.Context;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.view.View;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.TextView;import com.bumptech.glide.Glide;import com.cachecats.meituan.R;import butterknife.BindView;import butterknife.ButterKnife;/** * 上图片下标题的简单分模块布局自定义View */public class IconTitleView extends LinearLayout {    @BindView(R.id.iv_icon_title)    ImageView iv;    @BindView(R.id.tv_icon_title)    TextView tv;    private Context context;    public IconTitleView(Context context) {        this(context, null, 0);        this.context = context;    }    public IconTitleView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);    }    public IconTitleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        View view = View.inflate(context, R.layout.view_icon_title, this);        ButterKnife.bind(view);    }    public static IconTitleView newInstance(Context context, int imageResource, String title) {        IconTitleView iconTitleView = new IconTitleView(context);        iconTitleView.setImageView(imageResource);        iconTitleView.setTitleText(title);        return iconTitleView;    }    private void setImageView(int drawable) {        Glide.with(context).load(drawable).into(iv);    }    private void setTitleText(String title) {        tv.setText(title);    }}复制代码

IconTitleView的布局:

复制代码

这个是组合自定义View,比较简单,就不多说啦。

3. 两行图标的小模块 SmallModule

RecyclerView 实现的 GridView 布局,直接上代码吧。

/**     * 初始化小模块的RecyclerView     */    private void initLittleModuleRecyclerView() {        GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 5);        //设置LayoutManager        littleModuleRecyclerView.setLayoutManager(gridLayoutManager);        //设置分割器        littleModuleRecyclerView.addItemDecoration(new HomeGridDecoration(12));        //设置动画        littleModuleRecyclerView.setItemAnimator(new DefaultItemAnimator());        //设置Adapter        List
iconTitleModels = presenter.getIconTitleModels(); LittleModuleAdapter littleModuleAdapter = new LittleModuleAdapter( R.layout.view_icon_title_small, iconTitleModels); littleModuleRecyclerView.setAdapter(littleModuleAdapter); //设置item点击事件 littleModuleAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { @Override public void onItemClick(BaseQuickAdapter adapter, View view, int position) { ToastUtils.show(iconTitleModels.get(position).getTitle()); } }); }复制代码

LittleModuleAdapter.java

public class LittleModuleAdapter extends BaseQuickAdapter
{ private List
list; public LittleModuleAdapter(int layoutResId, @Nullable List
data) { super(layoutResId, data); list = data; } @Override protected void convert(BaseViewHolder helper, IconTitleModel item) { //设置图片 helper.setImageResource(R.id.iv_icon_title, item.getIconResource()); //设置标题 helper.setText(R.id.tv_icon_title, item.getTitle()); }}复制代码

都是 RecyclerView 的基本知识,就不再赘述了。

4. 四个广告封装的 HomeAdsView

HomeAdsView.java

public class HomeAdsView extends LinearLayout {    @BindView(R.id.ads_1)    ImageView ads1;    @BindView(R.id.ads_2)    ImageView ads2;    @BindView(R.id.ads_3)    ImageView ads3;    @BindView(R.id.ads_4)    ImageView ads4;    public HomeAdsView(Context context) {        this(context, null, 0);    }    public HomeAdsView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);    }    public HomeAdsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        View view = View.inflate(context, R.layout.view_home_ads, this);        ButterKnife.bind(view);    }    @OnClick({R.id.ads_1, R.id.ads_2, R.id.ads_3, R.id.ads_4})    public void onViewClicked(View view) {        switch (view.getId()) {            case R.id.ads_1:                onAdsClickListener.onAds1Click();                break;            case R.id.ads_2:                onAdsClickListener.onAds2Click();                break;            case R.id.ads_3:                onAdsClickListener.onAds3Click();                break;            case R.id.ads_4:                onAdsClickListener.onAds4Click();                break;        }    }    /**     * 设置广告的资源id,从左到右从上到下依次排列     * 加载本地图片     *     * @param list     */    public void setAdsResource(List
list) { if (list == null || list.size() != 4) { return; } Glide.with(this).load(list.get(0)).into(ads1); Glide.with(this).load(list.get(1)).into(ads2); Glide.with(this).load(list.get(2)).into(ads3); Glide.with(this).load(list.get(3)).into(ads4); } /** * 设置广告的资源id,从左到右从上到下依次排列 * 加载网络图片 * * @param list */ public void setAdsUrl(List
list) { if (list == null || list.size() != 4) { return; } Glide.with(this).load(list.get(0)).into(ads1); Glide.with(this).load(list.get(1)).into(ads2); Glide.with(this).load(list.get(2)).into(ads3); Glide.with(this).load(list.get(3)).into(ads4); } private OnAdsClickListener onAdsClickListener; public interface OnAdsClickListener { void onAds1Click(); void onAds2Click(); void onAds3Click(); void onAds4Click(); } public void setOnAdsClickListener(OnAdsClickListener onAdsClickListener) { this.onAdsClickListener = onAdsClickListener; }}复制代码

view_home_ads.xml

复制代码

向外暴露设置图片资源和Url地址的方法,并提供点击事件接口。用起来很简单:

private void initAds() {        homeAdsView.setOnAdsClickListener(new HomeAdsView.OnAdsClickListener() {            @Override            public void onAds1Click() {                ToastUtils.show("Ads1");            }            @Override            public void onAds2Click() {                ToastUtils.show("Ads2");            }            @Override            public void onAds3Click() {                ToastUtils.show("Ads3");            }            @Override            public void onAds4Click() {                ToastUtils.show("Ads4");            }        });    }复制代码

因为图片是写死的,这里只实现了点击事件回调。

5.团购信息列表

这个也是个普通的 RecyclerView,里面牵扯到数据库操作,就不在这里贴代码啦。 注意个问题,RecyclerViewScrollView 滑动会有冲突,需要特殊处理下,处理方法:

LinearLayoutManager lm = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false) {            @Override            public boolean canScrollVertically() {                return false;            }        }; rvShopList.setLayoutManager(lm);复制代码

通过设置 LinearLayoutManager 禁止RecyclerView 垂直方向上滑动。

6.下拉刷新加载更多

SmartRefreshLayout 实现的,它的官方文档写的很详细,本文重点在于解读主页,具体框架使用就不多说啦。


以上就是对美团首页布局分析及实现的过程,前四个模块说的比较详细,牵扯到自定义View的封装。其实不封装直接写也行,但为了后期维护起来不被人骂,还是多花点精力封装下吧。 团购信息列表和下拉刷新主要是普通的 RecyclerView 用法和框架整合,这类文章比较多,不明白的可以自行查阅相关资料。

源码地址:

欢迎下载,欢迎 star,欢迎点赞~

转载地址:http://uakwa.baihongyu.com/

你可能感兴趣的文章
COOK50小结
查看>>
JS使用正则表达式过滤多个词语
查看>>
知企业网上线了
查看>>
图形界面报错“已拒绝X11转移申请”的解决方法
查看>>
MongoDB整理笔记のDump & Restore
查看>>
如何获取codeforces的完整数据?(玄学方法)
查看>>
SSH无密码登陆
查看>>
拓扑图弹力布局呈现Flickr图片搜索结果
查看>>
分享数百个 HT 工业互联网 2D 3D 可视化应用案例
查看>>
享元模式(Flyweight)
查看>>
5-19 求链式线性表的倒数第K项 (20分)
查看>>
linux NFS 配置
查看>>
站立会议7
查看>>
linux客户端传输文件到Windows本地
查看>>
此生未完成
查看>>
c# 反射
查看>>
KVO的使用
查看>>
超图SuperMap Is.Net开发心得及一些问题
查看>>
问题2017S03
查看>>
【转载】工作中遇到的js问题以及问题的解决方案
查看>>