美文网首页Android开发Android开发经验谈Android开发
《一个Android工程的从零开始》阶段总结与修改1-base

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

作者: 半寿翁 | 来源:发表于2017-08-06 13:22 被阅读149次

    先扯两句

    最近在开发一个项目,算算时间也有一周没有继续写《一个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工程的从零开始》- 目录

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/koiglxtx.html