工作日志(2017-9)

     从一个美团跳到了另一个美团, 青岛确实比北京节奏慢几分, 可以将开始自己的工作日志, 记录点滴了, 虽不(一定)能至, (然诚)心向往之.

11th Monday

     一个流程图的绘制工具, 在线版本的, 可以绘制简单的流程图, 来自大兵的分享. 工具链接

13th Wednesday

    1. ConstraintLayout 的是用方式.

  • 与其他控件的约束(top, bottom, left, right, baseline )
  • 添加 GuideLine, 用百分比的方式.
  • baseline 约束仅仅适用于有基线的控件, 部分控件没有基线 (例如: spinner, ProgressBar 等)

     2. Switch 控件的开关位置, 明明是 ‘左开右关’, 但是 UI 一定要 ‘右开左关’, 还好有一个属性 LayoutDirect="rtl" 直接切换绘制方向, 搞定. New skill get~

    3. ProgressBar 自定义布局样式的 .xml 文件中, 有一个 <clip> 标签, 用于标注前景色, (如果不加这个标签, 那么通过 android:progressDrawable 设置的进度条的颜色会全部遮背景色, 也就是相当于 setValue(getMax()) 的显示状态.

15th Friday

    1. MVVM 在使用过程中出现一个bug:
     在 setValue(T value) 之后, 不更新 observer 的情况. debug 了一下源码, 发现有一个 mDispatchingValue 布尔类型的变量, 一直为真, 导致无法通知的情况. 源码如下: (setValue(T value) 会间接的调用到 dispatchingValue(..) 方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return; // 直接跳出循环.不再往下进行.
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
considerNotify(initiator);
initiator = null;
} else {
for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}

     把 setValue(T value) 的调用放在 onViewCreated() 中, 会出现这种情况. mDispathingValue 是一个私有变量, 应该只之前 setValue(T value) 方法被调用了一次, 再次调用出现直接返回的情况, 至于是何时何处调用, 目前上不可知.

     下午实践了下, 一个非常诡异的原因, 因为调用了 bt.setTag(1, obj) (语法错误, 应使用 R.id.), 如果调用 postValue(T value) 那么会直接崩掉, 如果使用 setValue(T value), 那么执行到 bt.setTag(1, obj) 语句时, 就直接返回了.
     应该是外层的一个 try-catch 语句, 把本来应该抛出的异常给清空了, 所以才会这个样子. 不过找了一下, 没有找到这个 try-catch 语句, 可能是系统的吧. 以后有机会在找找看.

16th Saturday

     周六还要来加班, 真心的不爽的说.
     1. 尝试了一下自定义 view, 发现其实没有想象的那么难. 需要做出来这个样子一个效果的 ProgressBar, 不过原生的 Android 是没有这个样子的样式的.



     在一个进度条上添加最大最小数值, 当完成后添加一个 ‘数据处理完成’ 的提示. 这两个效果自己手动添加, 其余的像是背景, 进度条颜色, 圆角. 都可以通过官方提供的方式来实现.
     问题不大, 仅仅使用到了一个 canvas.drawText(..) 方法, 定义 Paint 画笔的 Paint.Align.Right|Left 就可以实现左右对其的文字, 然后中线可以通过 onSizeChanged(..) 方法确定宽高, 进而可以计算出文字的基线. (感谢扔物线提供的相关教程. 自定义 view 之文字处理) 直接绘制出相应的文字就好.

     2. 有关 AppCompatButton/AppCompatTextView 的使用.
     下面是项目组大神强哥, 自定义 view 中的一段代码, 该自定义 view 继承 AppCompatButton

1
2
3
4
5
6
7
8
9
10
BaseActivity getBaseActivity(Context ctx) {
Context context = ctx;
while (context instanceof ContextWrapper) {
if (context instanceof BaseActivity) {
return ((BaseActivity) context);
}
context = ((ContextWrapper) context).getBaseContext();
}
throw new RuntimeException("Must use in BaseActivity");
}

     解释是, 在 5.0 以前, 初始化该 ViewContext 是一个 Context 对象, (听起来有点迷的样子), 而在 5.0 以后, 其实是一个 ContextWrapper, 需要做一定的处理, 才能获得对应 Activity/FragmentContext.

     3. 给自定义 View 添加自定义属性, 这个没有什么需要理解的, 只是熟练的程度而已, 有一个不错的指南, 已收藏, 如果下次再用, 直接翻出来看就好. 自定义 View 之自定义属性

18th Monday

     1. 自定义 Dialog 的相关事宜, 目前 Google 官方推荐使用 DialogFragment 来实现 Dialog 的相关功能.(其实很很早之前了的说). DialogFragment 的布局不像是 Activity / Fragment 那个样子, 内部会做一定的处理. 如果要自定义DialogFragmentDialog 的布局. 代码大体是这样:

1
2
3
4
5
6
7
8
9
10
11
12
public class ChangeHotKeyDialog extends DialogFragment {
// ... 省略了布局不相关的方法.
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_dialog, container, false);
unBinder = ButterKnife.bind(this, view);
return view;
}
}

     其中 R.layout.fragment_dialog 布局中, 根布局是一个 FrameLayout, 然后是一个指定宽高的子布局. 这个样子 Dialog 才会如预期的显示出来.
     如果不嵌套一个 FrameLayout 根布局(别的也可以, 只是 FrameLayout 最合适而已), 那么对话框就不会显示, 调试了一上午了. MD.

     2. recyclerView 的使用. 使用recyclerView 的三部曲:

  • 设置 LayoutManager
  • 设置 Adapter
  • 设置数据.

     今天的是用添加 header 和 footer 布局. 觉得还好, 虽然比 listView 多写了些东西, 但还感觉还是 hold 住的.

19th Tuesday

     1. 给 recyclerView 添加一个翻页效果, recyclerView 提供了非常人性化的接口 scrollToPosition(..), 在 LayoutManager 中, 可以直接调用, 但还调用没什么效果, 谷歌一下, 才发现需要用 scrollToPositionWithOffset(..) 方法, 配合 findLastVisibleItemPosition()findFirstVisibleItemPosition() 两个方法, 实现这个功能并不复杂.

     2. 坑爹的 EditText, 需要这样一个效果, 当向一个 EditText 输入数据时, 首字母大写, 其余的字母小写; 其实 Android 官方指定了 .xml 文件中实现方式, inputType="textCapWord 可是并没有什么卵用, 通过代码动态设置 setInputType(..) 也是不行的说, 貌似硬键盘输入可以转换, 软键盘不行, 所以绕过这个问题, 直接吧输入的结果转换成相应的形式就好.
     而且不能通过设置 onTextChangeListener 来实现, 因为可能会造成内存溢出, 和光标错乱. onTextChangeListener 中不要去修改 EditText, 只能是用来根据相应的文字做状态的判断.

     3. 有关于 building gradle info 的问题, 如果从 github 上下载下来, 一个项目, 导入的时候出现类似于下面的这张图.



     很恶心的有没有, 原因是因为你导入的项目中使用的 grade 版本, 你的项目中没有, 需要先下载, 而天朝的墙确实在是… 所以说我们需要手动的下载 gradle 包, 或者直接指定新项目中的 gradle 依赖就可以了. 项目中的 gradle-wrapper.properties 文件中的 gradle 依赖.
     这里顺便再说一下, Mac 电脑 gradle 的安装目录, 终端 cd .gradle/wrapper/dists/ 目录下, 指定的文件夹, 不过提醒一下, 记得gradle-wrapper.properties 中有一个 .zip 后缀.



20th Wednesday

     1. TabLayout + ViewPager + Fragment 的界面模式, 看起来挺简单的, 但是如果在 TabItem 上添加图片, 就比较麻烦了, 需要指定布局, 特殊处理.
     同时, 你也可能对 ToolBar 做一定的处理. Activity 中的代码看起来可能像是这个样子.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.settings);
// 去掉 tabLayout 的默认白色背景.
LinearLayout layout = (LinearLayout) mTabLayout.getChildAt(0);
layout.setBackgroundResource(R.color.transparent);
// 关联 viewpager 和 tabItem
mPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return items.get(position);
}
@Override
public int getCount() {
return items.size();
}
});
mTabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mPager));
mPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));

     注意一下, 这些个问题. 详细教程

     2. TextView 的设置, 如果给 setText() 传入一个非 String 类型的数据, 比如说整型, 那么该 TextView 不会显示数据.

     3. spinner 自定义样式. UI 给出的 spinner 多了一个三角, 如下图.



     这个三角形其实系统中的部分主题是带有这个三角的, 但是项目中主题制定了, 不能修改, 所以就用自定义 view 来实现的说. (主要是彰显一下我自定义 view 也是可圈可点的= =).
     本来打算重写 CheckedTextView 中的 onDraw() 方法, 加上一个三角形, 但是如果加载 CheckedTextView 上, spinner 的每隔下拉菜单也会有小三角, 所以就在 spinner 上画一个三角就好了, 用 Path 不难完成这个任务.

21 Friday

     1. 串口调用打印机(项目中有打印小票的逻辑.) 需要区分单字符和双字符(汉字, 英文以及他们的标点), 需要计算一个 string 的字节长度. 两种方式.

  • 直接转换成字节数组求长度.
  • 正则表达式.

     为了彰显自己是正则表达式小王子.(其实大神强哥说直接求字节数组长度就行了 TT, 做麻烦了.) 然后简化了下代码, 实现也是很完美的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 正则
public static int charNum(String str) {
Pattern pattern = Pattern.compile("[^\\x00-\\xff]");
Matcher matcher = pattern.matcher(str);
int sum = 0;
while (matcher.find()) {
sum++;
}
return sum + str.length();
}
// 字符数组
public static int charNum(String str) {
return str.getBytes().length;
}

     其中, 这个正则表达式 [^\\x00-\\xff] 是匹配双字节字符. 这个样子看来, 正则表达式也就是多了三四行代码的事儿.

25 Monday

     1. RxJava 和 Room 混合使用的一个问题.

1
2
3
4
Observable.just(dao.getData())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.MainThread())
....

     这个样子的一段代码, 直接 crash 掉, room 框架抛出了一个异常. 提示不可以在 UI 线程访问数据. 虽然我们指定了 just() 方法在工作线程中执行, 但是 java 编译器会先执行括号里面的代码, 所以会提示线程访问错误.
     有两种解决方案. 一是用 create() 发送一个 ObserveOnSubscribe. 二是用, just() 发送查询参数, 在 RxJava 的流中执行数据库查询操作.

     2. ObservableFlowable 的一个小区别. Flowable 有自动更新数据的功能, 如果用 room 返回一个 Flowable 对象时, 当表中的数据发生变换时, 会自动更新 MVVM 的 Observer, 可能会出现意想不到的问题.

29 Friday

     1. 再熟悉一下 Activity 的生命周期吧, 虽然是非常的熟悉了, 但是又在这个问题上卡住了.
     当 Activity A 启动 Activity B 时, 两个 Activity 生命周期的调用顺序为: A.onPause() –> B.onCreate() –> B.onStart() –> B.onPause() –> A.onStop()

     2. MVVM 模式中, ViewModel 中 onClear() 方法调用时机, 当没有 Activity 引用时, 就会自动调用.

~感谢捧场,您的支持将鼓励我继续创作~