- 注册页+忘记密码页
流程:输入手机号→发送验证码→输入验证码→下一步→输入密码→完成
异常处理:手机号格式错误,手机号已注册,发送验证码失败,发送验证码90s后才能再次发送,验证码错误,密码不合法,没有网络等。
架构使用了MVP模式,并且运用了依赖倒置原则。
布局用到的特殊属性:
TextView
android:drawableLeft
The drawable to be drawn to the left of the text.
TextView
android:drawablePadding
使用这2个属性可以在文字前加图片而无需加一个ImageView
。
The padding between the drawables and the text.
TextView
android:includeFontPadding
文字通常没有上标和下标,设成false后的TextView
的高度可以更符合标注的要求。
Leave enough room for ascenders and descenders instead of using the font ascent and descent strictly. (Normally true).
ViewGroup
android:addStatesFromChildren
下面的说明中就描述了这个属性的作用,可以使包含EditText
的外层布局获得焦点等。
Sets whether this ViewGroup’s drawable states also include its children’s drawable states. This is used, for example, to make a group appear to be focused when its child EditText or button is focused.
- 我的页+勋章页
1.两个勋章页均使用MVP架构,使用Picasso加载网络图片,使用Gson解析Json数据。
2.使用View.post(Runnable action)
方法,可以在页面加载完成之后再做对该View获取焦点等操作。具体原理可以参考【Andorid源码解析】View.post() 到底干了啥
View.post(Runnable action)
Causes the Runnable to be added to the message queue. The runnable will be run on the user interface thread.
3.使用SpannableString实现字符串的富文本效果
strings.xml
<string name="my_medal_overview">您一共获得了 %1$d 个勋章,其中%2$d年获得 %3$d 个,%4$d年获得 %5$d 个</string>
String s = getString(R.string.my_medal_overview, medals.size(), now.get(Calendar.YEAR) - 1, lastYear, now.get(Calendar.YEAR), thisYear);
SpannableString ss = new SpannableString(s);
ss.setSpan(new AbsoluteSizeSpan(48), s.indexOf(' ', start), s.indexOf(' ', s.indexOf(' ', start) + 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
myMedalOverviewText.setText(ss);
4.两个勋章页均使用单层RecyclerView
。我的勋章页初期考虑使用嵌套RecyclerView
,后来根据效果图改为单层RecyclerView
。
RecyclerView
的使用方法:
-
新建一个类继承
RecyclerView.Adapter<T extends RecyclerView.ViewHolder>
,重写onCreateViewHolder
、onBindViewHolder
和getItemCount
3个方法。 -
在
onCreateViewHolder
中,构造一个RecyclerView.ViewHolder
并将子View的布局传给它,然后返回这个ViewHolder
。 -
在
onBindViewHolder
中,根据position
的值对ViewHolder
设定数据和样式等。由于ViewHolder
会复用,当其重新出现在界面时,会保留复用前的数据,所以每次(即在该函数里)都要对所有数据和样式重新设定。 -
在
Adapter
中实现子View的View.OnFocusChangeListener
等,将这些listener传给ViewHolder
,可以增加复用并减少实例化。 -
新建这个
Adapter
的实例并通过RecyclerView.setAdapter(RecyclerView.Adapter adapter)
传给RecyclerView
。
5.在全部勋章页,采取4列的RecyclerView
布局,当焦点在第一行按向上键时,焦点会跑向左边。为解决此问题,对子View设置这个View.OnKeyListener
。
View.OnKeyListener onKeyListener = new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
int position = (int) v.getTag();
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
if (position < AllMedalActivity.COLUMN_COUNT) {
return true;
}
}
return false;
}
};
6.我的页,由于焦点框会盖住角标,因此新建一层FrameLayout
,放在content下。一开始这个Layout跟PageUser写在了一块,难以维护。后来根据面向对象的单一职责原则,将该FrameLayout
抽取出来,单独成立一个类BadgeFrame
。这个可以说是这个项目的一个难点。
ViewGroup contentView = (ViewGroup) view.getRootView().findViewById(android.R.id.content);
contentView.addView(this);
7.布局用到的特殊属性:
ViewGroup
android:descendantFocusability
Defines the relationship between the ViewGroup and its descendants when looking for a View to take focus.
Must be one of the following constant values.
Constant | Value | Description |
---|---|---|
afterDescendants | 1 | The ViewGroup will get focus only if none of its descendants want it. |
beforeDescendants | 0 | The ViewGroup will get focus before any of its descendants. |
blocksDescendants | 2 | The ViewGroup will block its descendants from receiving focus. |
ViewGroup
android:clipChildren
Defines whether a child is limited to draw inside of its bounds or not. This is useful with animations that scale the size of the children to more than 100% for instance. In such a case, this property should be set to false to allow the children to draw outside of their bounds. The default value of this property is true.
使用selector
,可参考Android中selector的使用