《一个Android工程的从零开始》阶段总结与修改1-base
2017-08-06 13:22
411 查看
先扯两句
最近在开发一个项目,算算时间也有一周没有继续写《一个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 e5f1 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__yizhi的Retrofit添加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
附录
《一个Android工程的从零开始》- 目录相关文章推荐
- 《一个Android工程的从零开始》阶段总结与修改2-Retrofit 上传JSON及尾址特殊字符转译问题
- 《一个Android工程的从零开始》-4、base(三) BaseActivity——Title
- 《一个Android工程的从零开始》-8、base(七) Retrofit的封装
- 《一个Android工程的从零开始》9、base(八) 数据存储-SharedPreferences
- 《一个Android工程的从零开始》-3、base(二) BaseActivity布局相关代码——空布局控件的运用
- 《一个Android工程的从零开始》-2、base(一) BaseActivity布局
- 《一个Android工程的从零开始》-5、base(四) BaseActivity——方法封装
- 《一个Android工程的从零开始》-6、base(五) BaseFragment封装
- 《一个Android工程的从零开始》-1前期准备
- 《一个Android工程的从零开始》- 目录
- Android学习阶段总结:自己做一个闹钟能学到什么?
- C++设计模式大总结_一个工程方便复习和修改
- Android APK逆向工程/反编译总结
- android 适配器Adpter的使用总结 之 BaseExpandableListAdapter
- maven 学习笔记(二)创建一个简单的 eclipse+android+maven 工程
- 购物网第一阶段总结笔记7:新闻管理模块之管理新闻(修改,删除)
- Android工程 引用另外一个Android工程
- 购物网第二阶段总结笔记5:用户个人资料修改页面、密码修改页面、用户积分页面、用户统计信息页面
- android按键驱动开发实例1(修改一个按键)
- 购物网第一阶段总结笔记4:友情链接模块之 友情链接的查询和修改