《一个Android工程的从零开始》阶段总结与修改1-base

先扯两句

最近在开发一个项目,算算时间也有一周没有继续写《一个android工程的从零开始》了,先跟大家道个歉。当然,这段时间真正实际操作中,也发现了自己的Base封装中有一些Bug,正好在这次开发过程中找出来,并且予以改正,特此发一篇博客说明一下,之前博客对应的部分会给予提示,部分内容就不予以修改了,算是为大家也为自己在出错的时候提供一个可查找对照的方向。错误的部分为大家使用过程中带来的不便十分抱歉。
闲言少叙,老规矩还是先上我的Git库,然后开始正文。
MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)

正文

这部分主要分为BaseActivity封装修改、BaseFragment封装修改、Retrofit header动态添加封装三部分。

BaseActivity封装修改

BaseActivity布局

BaseActivity中,布局修改的部分是ScrollView布局的部分,当然,这部分使用不会报错,只是无法实现我们要求,具体会出现什么问题,我创建了一个测试Activity,布局文件相当简单:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context="com.iyubatestapplication.ui.TestActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:text="大家注意看标题"
        android:textSize="100sp" />
</LinearLayout>

而java部分也是简单异常:

public class TestActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setBaseConten tView(R.layout.activity_test);
        setTitle("Test");
    }
}

那么我们来看看效果是什么样的:

这里写图片描述

大家可以看到,Title部分竟然也随着滑动了,这明显不是工程中我们想要的效果。
至于造成这个现象的原因,其实很简单,那就是之前集成的时候,我将ScrollView放在了最外层,也就是放在了Title的外面,那么ScrollView内的都会滑动,自然也就会出现Title随之滑动的现象了,既然好的了问题,那么解决起来自然就建简单了,只要将ScrollView放到Title下面一层不就好了嘛。不过真的这么简单吗?

这里写图片描述

这是京东的布局,如果套用我们刚刚的设想,那么大家思考一下,下方的工具栏在我们滑动的过程中会出现什么效果?没错,就如同上面的Title一样,会出现随着滑动的情况。当然,这是首页所特有的,想必大多数APP只会有一个首页,而且就功能而言,也就是“我的”版块才会用到ScrollView,而其他版块基本都会有RecyclerView(或者ListView、GridView、VLayout等),所以说这个部分我们可以单独搭建。
可是,如果当前的Activity中需要创建带有Title或者底部固定位置布局的Fragment,那么前面Title的问题就会再次出现,作为一个一直将偷懒作为人生准则的人,当然想要弄一个都考虑到的情况,所以这部分的布局代码就被我修改成了这样一副样子:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.banshouweng.mybaseapplication.base.BaseActivity">

    <include
        android:id="@+id/base_title_layout"
        layout="@layout/title_layout"></include>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:orientation="vertical">

        <LinearLayout
            android:id="@+id/base_main_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:visibility="gone"></LinearLayout>

        <ScrollView
            android:id="@+id/base_scroll_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone"></ScrollView>
    </FrameLayout>
</LinearLayout>

将LinearLayout与ScrollView放在一起,那么就可以想用哪里选哪里,当然,我们的setBaseContentView方法也要做一定的调整,成为了两个方法,setBaseContentView与setBaseScrollContentView,分别对应没有ScrollView和有ScrollView两种情况:

    /**
     * 引用头部布局
     *
     * @param layoutId 布局id
     */
    public void setBaseContentView(int layoutId) {
        LinearLayout layout = (LinearLayout) findViewById(R.id.base_main_layout);

        //获取布局,并在BaseActivity基础上显示
        final View view = getLayoutInflater().inflate(layoutId, null);
        //关闭键盘
        hideKeyBoard();
        //给EditText的父控件设置焦点,防止键盘自动弹出
        view.setFocusable(true);
        view.setFocusableInTouchMode(true);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
        layout.addView(view, params);
        layout.setVisibility(View.VISIBLE);
    }

    /**
     * 引用头部布局且当前页面基于ScrollView
     *
     * @param layoutId 布局id
     */
    public void setBaseScrollContentView(int layoutId) {
        ScrollView layout = (ScrollView) findViewById(R.id.base_scroll_view);

        //当子布局高度值不足ScrollView时,用这个方法可以充满ScrollView,防止布局无法显示
        layout.setFillViewport(true);

        //获取布局,并在BaseActivity基础上显示
        final View view = getLayoutInflater().inflate(layoutId, null);
        //关闭键盘
        hideKeyBoard();
        //给EditText的父控件设置焦点,防止键盘自动弹出
        view.setFocusable(true);
        view.setFocusableInTouchMode(true);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
        layout.addView(view, params);
        layout.setVisibility(View.VISIBLE);
    }

大家可以看得出来,我这里给方法的注释是“引用头部布局”也就是说,以上的所有讨论都是基于会引用我们的Title的情况下的,毕竟如果不使用Title的时候,我们可以直接使用setContentView方法进行布局初始化,当然,看过我之前博客的也会知道,我创建了一个hideTitle方法,用于处理Title的显隐。不过hideTitle + setBaseContentView就是setContentView,只有这种情况下,就建议大家直接使用setContentView,而hideTitle + setBaseScrollContentView则相当于在在setContentView的基础上嵌套了一层ScrollView,如果真的有需要,这个组合还是可以使用的。
当然,如果我们的布局中嵌套了ScrollView的情况下,在使用ListView与GridView的时候,会出现显示不全的情况等,这个部分想必大家都已经清楚了,而解决方法就是需要我们自定义一个自己的ListView,一般都为其命名为MyListView,而重写的方法也很简单,只需要继承ListView,并重写以下方法即可:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        try {
            int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
                    MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, expandSpec);
        } catch (Exception e) {

        }
    }

不过好在我最近使用RecyclerView的时候,故意没有重写一下,简单做了个测试,并没有发现ListView想同的问题,所以暂时就直接用了,如果大家对这部分有什么其他的发现,欢迎一起沟通。

ps:在工程开发过程中,尤其是电商APP的产品浏览,经常会上设置一键返回头部的按钮,这个按钮就很显然就不适合在ScrollView布局的内层,所以我们可以考虑将其集成在BaseActivity与Title一层,当然记得将父布局改成RelativeLayout,并在下方FrameLayout中添加属性android:layout_below=“@+id/base_title_layout”

ps的ps:以上建议是在本框架基础上进行的操作,当然也可以脱离框架自行编写一个布局,而且商品浏览一把都是有列表布局的,因此也不需要使用ScrollView,非要在上下添加其他布局的情况,RecyclerView可以判断onCreateViewHolder中的viewType做对应适配,ListView、GridView通用的方法就是添加Header,至于RecyclerView添加header的方法,大家可以看看张鸿洋大神的Android 优雅的为RecyclerView添加HeaderView和FooterView,或许会有帮助。

BaseActivity方法修改####

前面说了布局,是会影响到我们使用的,下面说的方法,呃,一部分也影响到我们使用,先上方法吧。

    /**
     * 最右侧文本功能键设置方法
     *
     * @param text          文本信息
     * @param clickListener 点击事件
     * @return 将当前TextView返回方便进一步处理
     */
    public TextView setBaseRightText(String text, View.OnClickListener clickListener) {
        TextView baseRightText = (TextView) findViewById(R.id.base_right_text);
        baseRightText.setText(text);
        baseRightText.setVisibility(View.VISIBLE);
        baseRightText.setOnClickListener(clickListener);
        return baseRightText;
    }

上面这个方法看过我前面博客的应该比较熟悉,那就是我在最右侧设置了一个文本功能键的使用方法,包括设置文本、设置点击时间等,不过若是细看的话,很简单就会发现与之前的不同。

第一:创建的baseRightText从全局变量变成了如今的局部变量,也就是说当我们不使用这个功能键的时候,虽然解析布局的时候依然会解析base_right_text对应id的TextView,不过至少(TextView) findViewById方法可以偷懒不执行了。

第二:TextView baseRightText = (TextView) findViewById(R.id.base_right_text),很显然这并不是ButterKnife的控件绑定方法,至于为什么这么写,主要还是因为我们无法在BaseActivity和集成它的Activity中同时添加ButterKnife.bind(this);,不然会出现如下错误:

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.banshouweng.mybaseapplication/com.banshouweng.mybaseapplication.ui.activity.MainActivity}: java.lang.IllegalStateException: Required view 'address_list' with ID 2131427418 for field 'addressList' was not found. If this view is optional add '@Nullable' (fields) or '@Optional' (methods) annotation.
...
Caused by: java.lang.IllegalStateException: Required view 'address_list' with ID 2131427418 for field 'addressList' was not found. If this view is optional add '@Nullable' (fields) or '@Optional' (methods) annotation.

原因就是在集成BaseActivity的Activity(以MainActivity为例)的onCreate,会有super.onCreate(savedInstanceState);方法,也就是说MainActivity的onCreate执行之前我们会先执行BaseActivity的onCreate方法,而BaseActivity中有ButtKnife.bind(this);方法,看过我之前的博客的会知道,我在BaseActivity中有这么一段代码:

    if (!(this instanceof MainActivity)) {
            activities.add(this);
    }

先把我查的this是什么贴出来:

this确实是当前activity的指针,它可以传给Context是因为Activity是Context的一个子类

也就是说当MainActivity继承BaseActivity时,this就是MainActivity,所以绑定控件的时候,就会以BaseActivity的布局文件作为参照,而这部分自然是没有MainActivity中对应的布局文件的,自然就会报上述错误。
反之,如果是在MainActivity中添加ButtKnife.bind(this);方法,而不再BaseActivity中添加,我们可以在下图位置查找到MainActivity_ViewBinding类:

这里写图片描述

在其中可以看到:

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public MainActivity_ViewBinding(MainActivity target, View source) {
    this.target = target;

    target.addressList = Utils.findRequiredViewAsType(source, R.id.address_list, "field 'addressList'", RecyclerView.class);
  }

  @Override
  @CallSuper
  public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.addressList = null;
  }
}

也就是说,其中只有我在MainActivity中的addressList控件,并没有BaseActivity中的控件,所以很遗憾,这样依然会报上述错误,因此,在BaseActivity中只能使用TextView baseRightText = (TextView) findViewById(R.id.base_right_text);关联控件与布局文件。

其三:@return 将当前TextView返回方便进一步处理
这个部分也比较好理解,有很多情况下,我们需要做界面的复用,也就是说右上角的图片功能键、文本功能键都不是固定的,需要修改资源,或者隐藏掉已经显示出来的功能键,而如果每次都创建一个对应的方法就得不偿失了,所以这里我们便将对应的功能键控件返回到调用的Activity中,需要处理的时候,处理对应返回的控件即可。

其四:baseRightText.setOnClickListener(clickListener);
这里不再创建对应的新接口,而是统一使用OnClickListener,当然,作为懒人,之前我一直懒得记每个对应控件的Id,不过第三点已经说了,我们将对应的控件返回了回来,所以只需要调用对应控件的getId()方法就可以很优雅的解决掉我们的偷懒问题。

BaseFragment封装修改

前面BaseActivity写了那么多,作为一个懒人,大家就别指望我在这里也会写那么多了,这里我只总结了一句话,那就是参见“BaseActivity封装修改”,对应做修改即可——和谐社会,不能打人的。

Retrofit header动态添加封装

这个部分呢,是最近有这方面需求,所以我就查看了一下,首先还是先感谢zhuhai__yizhiRetrofit添加header参数的几种方法。帮了个大忙。
大家可以通过zhuhai__yizhi了解一下对应的三种方法,而作为一个懒人的我,实在是太懒了,而且是集成的Retrofit2.3.0的我,就稍微尝试了一下,结果发现了一个更好的偷懒方法:

//Post
public interface RetrofitPostService {

    @FormUrlEncoded
    @POST("{action}")
    Observable<ResponseBody> postResult(@Path("action") String action, @HeaderMap Map<String, String> headerParams, @FieldMap Map<String, String> params);
}

//Get
public interface RetrofitGetService {
    @GET("{action}")
    Observable<ResponseBody> getResult(@Path("action") String action, @HeaderMap Map<String, String> headerParams, @QueryMap Map<String, String> params);
}

大家应该看出来了,那就是@HeaderMap Map<String, String> headerParams参数,用法就是Key是字段,Value是我们要传递的值,当然现阶段我接触到的,需要传递Header的还是比较少的,但是多了解一下总归是没有坏处的嘛。

附录

《一个Android工程的从零开始》- 目录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容