Gradle 学习

gradle wrapper 最主要的目的就是方便其他人更方便的快速编译整个项目,不用担心各种版本的不同

gradle wrapper 生成的文件有

  • gradlew (Unix Shell script)
  • gradlew.bat (Windows batch file)
  • gradle/wrapper/gradle-wrapper.jar (Wrapper JAR)
  • gradle/wrapper/gradle-wrapper.properties (Wrapper properties)

这些文件一定要提交到VCS中

生成的方法是使用 gradle wrapper --gradle-version 2.0 命令,自己也可以个性化这个task,更多个性化的内容参见Wrapper的API文档

1
2
3
task wrapper(type: Wrapper) {
gradleVersion = '2.0'
}

此后可以直接使用 ./gradlew XXX 进行编译,可以将gradlew看成一个包有gradle命令的壳

需要注意的是,如果你在使用其他人生成好的gradle项目的时候,初次运行./gradlew build 会下载对应版本的gradle,下载的文件会存放在~/.gradle/wrapper/dists


从 Formatter 学习 TextWatcher

电话号码和银行号码经常会有需要格式话为固定的样式,比如银行卡号通常会格式化为 XXXX XXXX XXXX XXXX,手机号通常会格式化为 XXX XXXX XXXX,对于手机号码可以用 Android 自带的 PhoneNumberFormattingTextWatcher, 但是这种格式化的方式有时候并不是我们想要的,那就需要自己来实现一个 TextWatcher 了。

第一次尝试实现

先尝试实现一次,在每次text有更新的时候做一次 format, 代码如下(省略 import 的内容):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class FormatterTest extends Activity {

private final char SEPARATOR = ' ';

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.formatter_test);
EditText editText = (EditText) findViewById(R.id.phone_num);
editText.addTextChangedListener(new TextWatcher() {

private boolean mStopFormatting;

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {

}

@Override
public void afterTextChanged(Editable s) {
if(mStopFormatting) {
return;
}
mStopFormatting = true;
format(s);
mStopFormatting = false;
}
});
}

private String format(String s) {
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
format(stringBuilder);
return stringBuilder.toString();
}

private void format(Editable s) {
clean(s);
if (s.length() < 4) {
return;
}
int i = 0;
int valid = 0;
while (i < s.length()) {
if (valid % 4 == 0 && valid != 0) {
s.insert(i, Character.toString(SEPARATOR));
i++;
}
i++;
valid++;
}
}

private void clean(Editable s) {
int p = 0;
while (p < s.length()) {
if (!Character.isDigit(s.charAt(p))) {
s.delete(p,p+1);
}
p++;
}
}
}

运行之后可以正常的format,不过将光标移动到空格位置删除的时候却无法删除。
原因就是实际上是删除成功了,但是在 afterTextChanged 的时候又格式化了,因此空格在删除后又会出现,给人的感觉就是无法删除。


背景颜色随滑动而渐变的View

名称: GuideBackgroundColorAnimation

分类: 动画

项目地址: https://github.com/TaurusXi/GuideBackgroundColorAnimation

功能: view的背景色能随着滑动而渐变, 适合引导页的设计,稍作改动也可用于栏目的切换.

分析:

这个小功能没有特别复杂的地方, 主要是在ViewPager的监听函数onPageScrolled()这里实现了颜色的改变.分析下过程吧:

  1. 自定义一个View—ColorAnimationView
  2. 为view设置一各Viewpager, 并重写ViewPager.OnPageChangeListener
  3. 重点分析onPageScrolled():
1
2
3
4
5
6
int count = getViewPagerChildCount() - 1;
if (count != 0) {
float length = (position + positionOffset) / count;
int progress = (int) (length * DURATION);
ColorAnimationView.this.seek(progress);
}

先得到页面数量count, 根据每一次的positionpositionOffset计算得到length, position是从0开始计算, 最大值为count - 1, 如果有n个子页面, position的最大值就是n-1. 而positionOffset则是一个取值范围在[0,1]之间的值, 对于一个页面来说, 滑动到一半, positionOffset的值就是0.5, 全部滑过来的那一瞬间, 取值为1, 当然也是下一个新的position的0了. 因此length可以想象成一个取值范围为[0,count]/count(也就是[0,1])之间的浮点数, 能反映全部过程中的某一个时间点.
根据这个百分比,再乘以总的时间DURATION就知道目前完成了整个动画的百分比了, 也就是progress, 随后调用seek方法.


Handler和Timer作为计时器的优劣

Android中可以采用Timer+Handler或者单纯使用Handler的方法来实现定时通知的功能,但是采用Handler的方法会更好,优点不仅仅在于代码量会少很多,更主要在使用于Timer会产生bug。

使用Timer+Handler的方式

这种方式在TimerTask的run方法中sendMessage给Handler,实现定时操作的目的,但是使用Timer本身会出现一种问题,看下面的实验代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
print("first");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
print("second");
}
};

Timer timer = new Timer();
timer.schedule(timerTask, 0, 1000);

愿意是想立即执行一次这样的任务:先输出first,隔2秒再输出second,然后再隔1秒,再执行一次同样的任务,但是当执行过后就会发现,second输出完之后会马上输出first,并没有延迟1秒,问题就在于timer的时间延迟是从任务开始执行时就已经计算了,并不是上一次任务执行完成后才计算的,因此这个任务执行了2秒,其实第二次任务已经开始执行了,但是是单队列,所以并没有马上出现,但是下一次已经不会再如我们所愿的延迟1秒了(但是它其实也按照它自己的方式延迟了1秒,也就是它不在乎任务有没有执行完,我自己发出命令就开始计算1秒,然后再发出命令),因此这种方式可能会出现问题。

只使用Handler的方式

这种方式既简单也没有风险:

1
handler.sendEmptyMessageDelayed(MSG_TIME_COUNT, 1000);

一句代码即可搞定,会延迟1秒发送消息给handleMessage来处理,而且每次是等任务执行完才会开始延迟。