resources标签

  • 所有的资源文件,都可以放在同一个<resources>标签下面;但放到不同的文件中,更方便查找
    例如:colordeclare-styleabledimenintegeritemstringstyle

string标签translatable属性

使用translatable="false"属性可以让内容直接嵌入到引用的地方,而不做翻译处理。

  • 举例来说,下面的用法就可以将android.support.design.widget.AppBarLayout$ScrollingViewBehavior直接嵌入到了ScrollView
1
2
3
4
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
1
<string name="appbar_scrolling_view_behavior" translatable="false">android.support.design.widget.AppBarLayout$ScrollingViewBehavior</string>
  • 另一种方案:

    It’s the ignore attribute of the tools namespace in your strings file, as follows:

    1
    2
    3
    4
    5
    6
    <?xml version="1.0" encoding="utf-8"?>
    <resources
    xmlns:tools="http://schemas.android.com/tools"
    tools:ignore="MissingTranslation" >
    <!-- your strings here; no need now for the translatable attribute -->
    </resources>

使用场合

不希望字符串被翻译。

外部链接

string动态设置下划线文本的方案;

1
2
<!-- 还可以使用:<b>、<i>、<u>等html标签 -->
<string name="nav_name"><u>%1$s</u></string>
1
2
3
4
5
6
7
<TextView
android:id="@+id/tv_nav_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nav_name"
android:textColor="@color/colorPrimary"
android:textSize="18sp"/>
1
2
3
// 动态控制带下划线的文本内容
String name = "昵称";
tvName.setText(mContext.getString(R.string.nav_name, name));

使用string字符串资源的一些建议

  • 不同模块相同的文字不要复用,最好每个模块都对应自己的字符串资源(否则,牵一发而动全身,除非你就想要这样);

string字符串中的单复数

  • 单纯引用
    1
    2
    3
    4
    <plurals name="book">
    <item name="one">book</item>
    <item name="others">books</item>
    </plurals>
1
2
3
int bookCount = 4;
mContext.gerResources().getQuantityString(R.plurals.book, bookCount);
//~ result: books
1
2
3
4
<plurals name="book">
<item name="one">%d book found.</item>
<item name="others">%d books found.</item>
</plurals>
1
2
3
int bookCount = 4;
mContext.gerResources().getQuantityString(R.plurals.book, bookCount, bookCount);
//~ result: 4 books found.

When using the getQuantityString() method,
you need to pass the count twice if your string includes string formatting with a number.
For example, for the string %d songs found,
the first count parameter selects the appropriate plural string
and the second count parameter is inserted into the %d placeholder.
If your plural strings do not include string formatting,
you don’t need to pass the third parameter to getQuantityString.

外部链接

双击 view 执行 runnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void setDoubleClickRunnable(View view, Runnable task) {
new DoubleClick().click(view, task);
}

private static class DoubleClick {
private int count = 0;

// 双击 View 触发 task
void click(View view, Runnable task) {
view.setOnClickListener(v -> {
if (++count >= 2) {
task.run();
return;
}
new Timer().schedule(new TimerTask() {
@Override
public void run() {
count = 0;
}
}, 500);
});
}
}

例如:双击回到顶部

1
2
// 双击 toolbar,让 webivew 回到顶部
Uview.setDoubleClickRunnable(mToolbar, () -> mWebView.scrollTo(0, 0));

按返回键,返回到桌面而不销毁程序

解决办法:在需要退出的地方,使用Activity.moveTaskToBack(true)方法

具体代码:

1
2
3
4
5
6
7
8
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK && mContext.isTaskRoot()){
moveTaskToBack(true);
return true;
}
return super.onKeyDown(keyCode, event);
}

外部链接

跳过欢迎界面时机

原理:使用 Application 的生命周期。(在后台未被清理的时候,再次打开程序时 Application 的onCreate()方法不会再执行。)

关于 Application 的 onCreate()方法:
在应用的第一个组件启动之前,系统会先调用此方法。

外部链接

给 Activity 添加快捷方式到桌面

外部链接

运行 java 文件

在 Android Studio 中的 app module 中运行 Java 测试代码,会发现很慢;
可以尝试创建 java library 模块来运行 java 测试今古代码;

添加mContext

  • 在Activity和Fragment中,经常需要用到上下文信息;
    虽然通过类名.this的方式虽然也可以,但是mContext将更方便易懂,尤其是将代码拷贝来拷贝去时;

  • mContext声明在基类中(例如:BaseActivityBaseFragment):

    1
    protected Activity mContext;

在BaseActivity的onCreate方法中添加:

1
mContext = this;

或者在BaseFragment中的onCreate中添加:

1
mContext = getActivity();

这样所有继承自BaseActivityBaseFragment的类,均可以直接使用mContext了;

  • 扩展:类似的方法,可以让所有的子类直接使用TAG,而不需要在自己的类中声明和初始化;
    BaseActivity中添加:
    1
    protected final String TAG = getClass().getSimpleName();

外部链接

BroadcastReceiver

  • 广播是一种可以跨进程的通信方式。(引用自:《第一行代码》p199)

需要注意的是,不要在onReceiver()中添加过多的逻辑或进行任何耗时操作,因为在广播接收器中是不允许开启线程的,
onReceiver()方法运行了较长时间而没有结束时,程序就会报错。因此,广播接收器更多的是扮演一种打开其他组件的角色,
比如创建一条状态栏通知,或者启动一个服务等。(引用自:《第一行代码》p196)

动态修改 shape 中的颜色

1
2
3
4
5
6
<!-- drawable/opera_circle.xml -->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#fff002" />
<size android:height="64dp" android:width="64dp"/>
</shape>
1
2
3
4
5
6
7
8
<!-- layout/ -->
<ImageView
android:id="@+id/item_mode_list_iv_color"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="28dp"
android:src="@drawable/opera_circle"/>
1
2
3
ImageView ivColor = getView(R.id.item_mode_list_iv_color);
GradientDrawable drawable = (GradientDrawable) ivColor.getDrawable();
drawable.setColor(Color.parseColor("#2b3c89"));

动态修改 Selector –> layer-list –> shape 中的颜色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- drawable/selector_skin_color.xml -->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" >
<layer-list >
<item android:left="4dp" android:top="4dp" android:right="4dp" android:bottom="4dp">
<shape android:shape="oval">
<solid android:color="@android:color/transparent"/>
</shape>
</item>
</layer-list>
</item>

<item android:state_checked="false" >
<layer-list >
<item android:left="12dp" android:top="12dp" android:right="12dp" android:bottom="12dp">
<shape android:shape="oval">
<solid android:color="@android:color/transparent"/>
</shape>
</item>
</layer-list>
</item>
</selector>
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

public RadioButton getCircleRadioButton(int color) {
Context context = getContext();
int w = Uscreen.dp2Px(context, 48);
RadioButton rbtn = new RadioButton(context);
rbtn.setLayoutParams(new RadioGroup.LayoutParams(w, w));
rbtn.setButtonDrawable(new ColorDrawable(Color.TRANSPARENT));
rbtn.setBackgroundResource(R.drawable.selector_skin_color);

// 动态修改颜色;
Drawable drawable = rbtn.getBackground();
if(drawable instanceof StateListDrawable){
StateListDrawable gradientDrawable = (StateListDrawable) drawable;
ConstantState constantState = gradientDrawable.getConstantState();
if(constantState instanceof DrawableContainerState){
DrawableContainerState drawableContainerState = (DrawableContainerState)constantState;
Drawable[] children = drawableContainerState.getChildren();
for (int i = 0; i < children.length; i++) {
if (children[i] instanceof LayerDrawable) {
LayerDrawable selectedItem = (LayerDrawable) children[i];
GradientDrawable selectedDrawable = (GradientDrawable) selectedItem.getDrawable(0);
selectedDrawable.mutate();
selectedDrawable.setColor(color);
}
}
}
}

return rbtn;
}

具体应用参考:【Android】自定义 View —— 设置中的选择皮肤项

让 Selector 中的图片居右对齐

抛出问题:
实现如图的效果和功能,当选中时后面有一个对勾,当非选中时没有对勾,点击「发送」的时候给所有选中的 item 发送消息。
让Selector中的图片居右对齐

复杂的做法是:
通过自定义一个实体类,每个对象都有一个名称属性和一个表示是否是选中状态属性;
给 ListView 设置 item 监听,点击 item 的时候,改变其状态然后刷新列表 ……

简单的做法:
因为这里只是为了获取到所有选中的 item,并没有其他的功能需求,我们可以充分利用 ListView 的 choice 功能,
(即通过设置 ListView 的多选模式android:choiceMode="multipleChoice");

为了使用简单的做法,我们可能会遇到这样的问题:在哪里设置选中和没选中两种状态;
解决办法就是给 item 的根布局设置 selector,根据是否处于激活状态来区分;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- layout/item_lv-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/llyt_bg_selector"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingTop="8dp" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="浩南"/>
</LinearLayout>

如何让 selector 中的图片居右对齐:
简单来说,就是利用 bitmap 标签的 gravity 属性和 titleMode 属性来控制。

1
2
3
4
5
6
7
8
9
10
11
<!-- drawable/llyt_bg_selector.xml -->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_activated="true">
<bitmap android:src="@drawable/img_checked"
android:tileMode="disabled"
android:dither="true"
android:antialias="true"
android:gravity="end|center_vertical"/>
</item>
</selector>

如何获取所有选中的条目:

1
2
3
4
5
6
7
8
9
SparseBooleanArray sba = listView.getCheckedItemPositions();
ArrayList<Friend> checkedLists = new ArrayList<>();

for (int i = 0; i < friendAdapter.getCount(); i++) {
if (sba.get(i)) {
checkedLists.add(adapter.getItem(i));
}
}
Log.e("Lou", "Get checked items :" + checkedLists);

还有一种思路:
通过「点 9」图来实现让图片居右对齐的目的;

外部链接:

背景图片平铺

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/stripes"
android:tileMode="repeat" />

推荐教程

点 9 图-点九图

我们可以在图片的四个边框绘制一个个的小黑点,
在上边框和左边框绘制的部分就表示当图片需要拉伸时就拉伸黑点标记的区域
在下边框和右边框绘制的部分则表示内容会被放置的区域

TextView

字符串资源里的变量替换

在 xml 中定位占位符(其中1表示第一个变量,多个变量递增表示)

1
2
<!-- values/strings.xml -->
<string name="replace_str">你好,%1$s:欢迎您!</string>

java 代码中动态指定%1$s处的值

1
String str = getString(R.string.replace_str, "小明");

使用 HTML 格式化文本

1
textView.setText(Html.fromHtml(HTML_STR));

跑马灯效果不生效

需要在代码中设置tv.setSelected(true)

TextView Marquee not working

外部链接

TextView 设置空格

1
&#160;&#160;&#160;&#8201;&#160;&#160;&#8201;

TextView html 渲染(注意:html 渲染的方式无法改变字体的大小,可以调整颜色、粗细、斜体等属性)

1
2
3
4
5
6
7
public static void renderWithHtml(final TextView tv, String data) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
tv.setText(Html.fromHtml(data, Html.FROM_HTML_MODE_COMPACT));
} else {
tv.setText(Html.fromHtml(data));
}
}

TextView 渲染字体

Android TextView 个别字体格式设置小结 - 简书

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
SpannableString msp = new SpannableString("字体测试字体大小一半两倍前景色背景色正常粗体斜体粗斜体下划线删除线x1x2电话邮件网站短信彩信地图X轴综合");

//设置字体(default,default-bold,monospace,serif,sans-serif)
msp.setSpan(new TypefaceSpan("monospace"), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
msp.setSpan(new TypefaceSpan("serif"), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

//设置字体大小(绝对值,单位:像素)
msp.setSpan(new AbsoluteSizeSpan(20), 4, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
msp.setSpan(new AbsoluteSizeSpan(20, true), 6, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //第二个参数boolean dip,如果为true,表示前面的字体大小单位为dip,否则为像素,同上。

//设置字体大小(相对值,单位:像素) 参数表示为默认字体大小的多少倍
msp.setSpan(new RelativeSizeSpan(0.5f), 8, 10, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //0.5f表示默认字体大小的一半
msp.setSpan(new RelativeSizeSpan(2.0f), 10, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //2.0f表示默认字体大小的两倍

//设置字体前景色
msp.setSpan(new ForegroundColorSpan(Color.MAGENTA), 12, 15, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //设置前景色为洋红色

//设置字体背景色
msp.setSpan(new BackgroundColorSpan(Color.CYAN), 15, 18, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //设置背景色为青色

//设置字体样式正常,粗体,斜体,粗斜体
msp.setSpan(new StyleSpan(android.graphics.Typeface.NORMAL), 18, 20, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //正常
msp.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 20, 22, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //粗体
msp.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 22, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //斜体
msp.setSpan(new StyleSpan(android.graphics.Typeface.BOLD_ITALIC), 24, 27, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //粗斜体

//设置下划线
msp.setSpan(new UnderlineSpan(), 27, 30, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

//设置删除线
msp.setSpan(new StrikethroughSpan(), 30, 33, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

//设置上下标
msp.setSpan(new SubscriptSpan(), 34, 35, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //下标
msp.setSpan(new SuperscriptSpan(), 36, 37, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //上标

//超级链接(需要添加setMovementMethod方法附加响应)
msp.setSpan(new URLSpan("tel:4155551212"), 37, 39, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //电话
msp.setSpan(new URLSpan("mailto:webmaster@google.com"), 39, 41, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //邮件
msp.setSpan(new URLSpan("http://www.baidu.com"), 41, 43, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //网络
msp.setSpan(new URLSpan("sms:4155551212"), 43, 45, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //短信 使用sms:或者smsto:
msp.setSpan(new URLSpan("mms:4155551212"), 45, 47, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //彩信 使用mms:或者mmsto:
msp.setSpan(new URLSpan("geo:38.899533,-77.036476"), 47, 49, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //地图

//设置字体大小(相对值,单位:像素) 参数表示为默认字体宽度的多少倍
msp.setSpan(new ScaleXSpan(2.0f), 49, 51, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //2.0f表示默认字体宽度的两倍,即X轴方向放大为默认字体的两倍,而高度不变
//SpannableString对象设置给TextView
tokenTv.setText(msp);
//设置TextView可点击
tokenTv.setMovementMethod(LinkMovementMethod.getInstance());

Android 开发,你遇上 Emoji 头疼吗? - 掘金

ImageView

交叉使用 mipmap 和 drawable

钻牛角尖:先必须获取到之前的 Drawable,然后将这个 Drawable 进行转换,然后进行图片替换;
解决思路:通过覆盖的单向方式,而不用知道之前是图片资源还是 Drawable 资源。

1
2
3
4
5
6
ImageView ivMain = findViewById(R.id.iv_main);
if(满足条件){
ivMain.setBackgroundResource(R.id.selected);
} else {
ivMain.setBackgroundResource(R.id.unselected);
}

ImageView 加载 GIF 图片

代码

1
2
3
4
5
ImageView iv = (ImageView) findViewById(R.id.vp_iv);
Glide
.with(this)
.load("https://i.imgur.com/l9lffwf.gif")
.into(iv);

外部链接

加载网络图片

代码

1
2
3
4
5
Picasso
.with(this)
.load("https://i.imgur.com/l9lffwf.gif")
.placeholder(R.mipmap.ic_launcher)
.into(iv);

外部链接


EditText

定位光标位置:

1
2
3
String name = "Lou";
EditText et = (EditText)findViewById(R.id.et_name);
et.setSelection(name.length()); // 将光标至于文字最后

使光标颜色和文字颜色保持一致(EditText 不显示光标问题):

1
2
3
<!-- 有的时候发现EditText里的光标无法显示的问题,很可能是光标的颜色和背景重合了,可以通过设置光标的颜色属性来让其显示 -->
<!-- 在EditText标签中添加如下属性 -->
android:textCursorDrawable="@null"

外部链接


ListView

ListView 中不可见的元素,其对应的 view 为 null。这是容易理解的,性能优化。
(在 updateItem 的时候要做两方面的处理,即数据(updateItemData)和视图(updateItemView))

添加空白

  • 在 ListView 的顶部和底部添加空白(见外部链接)
  • 在 Item 之间添加空白(通过 Divider 的方式)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/alarm_bg"
android:orientation="vertical">

<ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"

android:paddingTop="16dp"
android:paddingBottom="16dp"
android:clipToPadding="false"

android:scrollbars="none"
android:divider="@android:color/transparent"
android:dividerHeight="10dp"
/>
</LinearLayout>

外部链接


修改 DatePicker 日期选择器默认样式(同理适用于 TimePicker)

效果图

DatePicker

代码

1
2
3
4
5
6
<!-- 使用Holo样式 -->
<DatePicker
android:id="@+id/dialog_personal_birth_dp"
style="@android:style/Widget.Holo.DatePicker"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
1
2
3
4
5
6
7
8
9
private String mBirth = "1981.12.11";
final DatePicker dp = dialogBirth.getView(R.id.dialog_personal_birth_dp);
dp.setCalendarViewShown(false); // 不要显示Calendar视图
Uview.changeTimePickerSepColor(dp, Color.DKGRAY); // 修改分割线样式
String[] birth = mBirth.split("\\.");
dp.init(Integer.parseInt(birth[0]),
Integer.parseInt(birth[1]) - 1,
Integer.parseInt(birth[2]),
null);
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
//: Uview.java
public static void changeTimePickerSepColor(ViewGroup group, int color) {
for (NumberPicker np : getNumberPickers(group)) {
changeNumberPickerSepColor(np, color);
}
}


private static List<NumberPicker> getNumberPickers(ViewGroup group) {
List<NumberPicker> lists = new ArrayList<NumberPicker>();
if (group == null) {
return lists;
}

for (int i = 0; i < group.getChildCount(); i++) {
View v = group.getChildAt(i);
if (v instanceof NumberPicker) {
lists.add((NumberPicker) v);
} else if (v instanceof LinearLayout) {
List<NumberPicker> ls = getNumberPickers((ViewGroup) v);
if (ls.size() > 0) {
return ls;
}
}
}
return lists;
}

public static void changeNumberPickerSepColor(NumberPicker np, int color) {
Field[] pickerFields = NumberPicker.class.getDeclaredFields();
for (Field f : pickerFields) {
if (f.getName().equals("mSelectionDivider")) {
try {
f.setAccessible(true);
f.set(np, new ColorDrawable(color));
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}

// 分割线粗细
for (Field f : pickerFields) {
if (f.getName().equals("mSelectionDividerHeight")) {
try {
f.setAccessible(true);
f.set(np, 1);
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
}

给 imageview 设置 tag 值来传值

ivSetting.setTag(room.getId());
这样在进入设置界面时,可以通过 getTag 的方式获取到 room 的 id。

http://www.vogella.com/tutorials/Retrofit/article.html#create-activity

主题中使用 tint 会导致 imageview 显示空白

1
<item name="android:tint">#ffffff</item>

书签

更多的是为了搜索,所以可以不加分类的保存起来,有必要的话给书签添加关键字;
(除了少数常用的放在书签栏上之外)

插件

  • OneTab
  • 直接访问(直接访问 Google 搜索结果。)
  • Markdown Preview Plus
  • cVim
  • Codota
  • AdBlock
  • Checker Plus for Gmail
  • Checker Plus for Calendar
  • crxMouse Chrome Gestures
  • Diigo Web Collector
  • Markdown Here
  • Markdown Preview Plus
  • Momentum
  • Octotree
  • 夜景模式
  • Proxy SwitchyOmega

控制台

快捷键

  • Focus in console: Ctrl + `
  • 清空屏幕: Ctrl + L
  • 清除已经声明的变量:console.clear();

调试

进入调试模式: F12

JS 调试: 可以在 Snippets 里编写 Javascript 脚本,运行(Ctrl+Enter),和打断点调试

摘要:

主要内容:
本文介绍了个人的 Android Studio 配置,包括主题、插件等;

用途

  • 编写 Android 代码;
  • 写 hexo 上的文章;

快捷键

  • !j

    Sublime Text 式的多处选择(Sublime Text Multi Selection)
    描述:这个功能超级赞!该操作会识别当前选中字符串,选择下一个同样的字符串,并且添加一个光标。这意味着你可以在同一个文件里拥有多个光标,你可以同时在所有光标处输入任何东西。
    快捷键:Ctrl + G(OS X)、Alt + J(Windows、Linux)

  • !+insert

    列选择/块选择(Column Selection)
    描述:正常选择时,当你向下选择时,会直接将当前行到行尾都选中,而块选择模式下,则是根据鼠标选中的矩形区域来选择。
    调用:按住 Alt,然后拖动鼠标选择。
    开启/关闭块选择:Menu → Edit → Column Selection Mode
    快捷键:切换块选择模式:Cmd + Shift + 8(OS X)、Shift + Alt + Insert(Windows/Linux);

  • !q 上下文信息;

  • ^!m 提取方法;

  • ^!p 提取参数(window 快捷键冲突);

  • ^!n 内置(inline),提取的反操作;

  • ^+j 合并行和文本;

  • ^!t 包裹代码(Surround With);

  • ^+delete 移除包裹代码(包裹代码的反操作);

  • +F4 对当前打开的文件另起一个窗口打开;

  • ^+insert: 选择最近复制的内容进行粘贴;

  • ^+f: Find in Path

  • ^+r: Find and Replace in Path

  • askey

  • askey

Theme

  • Theme: Darcula
  • Font: Fira Code (Enable font ligatures), (14, 1.1)

Plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# basic
- .ignore
- CamelCase
- GsonFormat
- Json to Kotlin Class
- BugKotlinDocument
- Parcelable Code Generator​(for kotlin)
- Markdown support
- BashSupport

# other
- Android ButtkerKnife Zelenzy
- Markdown Navigator
- MultiTypeTemplates
- Smalidea

Tips

  • Editor->General->Smart Keys
  • 使用 Refector (style)的正确姿势:
    将鼠标定位到要 refector 的标签中(不要选中任何代码),
    然后右键 Refector --> Extract --> Style...
  • 通过 debug 的方式调试和查看数据值,而不是通过繁琐地打印日志;

提升 Gradle 编译速度

  • 设置代理
1
2
3
4
5
6
# gradle.properties中添加
# 举例ShadowSocket
systemProp.http.proxyHost=127.0.0.1
systemProp.http.proxyPort=8118
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=8118
  • 设置中启用离线状态
  • 启用守护进程
1
2
3
4
# gradle.properties中添加
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.daemon=true
  • 编译 SDK 使用 21 以上

外部链接

logcat color

search: Logcat

1
2
3
4
5
Assert: 9C27B0
Debug: 2196F3
Error: F44336
Info: 4CAF50
Warning: FFC107

摘要:

主要内容:
对书上的重点和难点内容进行总结和回顾;

任务列表:

  • 优化文档结构(2016.05.26)

第二章

每一次学习 , 都会有新的收获;
我相信我看到的奇迹,没有是平白无故产生的,它们是源于环环相扣的逻辑巧妙组合而成。

隐藏标题栏(p35)

在调用 setContentView()之前,写上下面代码:
requestWindowFeature(Window.FEATURE_NO_TITLE);

========== 更新 2016.05.23 10:42:53 =============
也可以通过配置主题的方式应用到所有 Activity 中:
AppTheme中添加:

1
2
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/white</item>

隐式 Intent(p44)

隐式里的 category 和 data 就像是更精确的过滤器

保存临时数据 (p67)

当启动新的活动时,当前的活动数据如果不加以保存,那么就会有可能丢失掉数据(系统回收)。
这样的用户体验不好,可以通过在onSaveInstanceState()方法中保存数据来解决这个问题。(重要的数据还是在 onPause 中保存的好,在 onPause 中不要进行耗时操作)
相应的,在onCreate(Bundle)中进行恢复操作。

活动启动模式(p68)

使用方式:在 Manifest.xml 的对应 activity 中添加android:launchMode="standard|singleTop|singleTask|singleInstance"
参数说明:

  1. standard 模式:无论返回栈中是否存在该活动,每次启动都会创建一个新的实例(例如,自己启动自己,也会创建多个实例出来)
  2. singleTop 模式:如果返回栈的栈顶是要启动的活动,则直接使用它,而不是再创建一个新的活动实例出来。
  3. singleTask 模式:使该活动在整个应用程序的上下文中只存在一个实例,如果不在栈顶,则将该活动上面的所有活动出栈,如果没有该活动,则创建一个活动实例。
  4. singleInstance 模式:指定为该模式的活动就启用新的返回栈来管理这个活动。(还不是很了解)

技巧:

进入某个界面在日志中打印对应的 Activity 的名称(方便定位) (p77):

原理: 简单来说是通过 java 的重写机制 。
具体步骤:

  1. 创建一个基类 Activity,例如BaseActivity
  2. 在基类 Activity 的 onCreate()方法中,添加代码Log.d("BaseActivity", getClass().getSimpleName())

随时随地退出程序(p78):

利用 List 作为保存 Activity 的容器:一旦有 Activity 被创建,就加入到容器中;对应的,一旦 Activity 被销毁,就从容器中移除 。在想要退出的地方,逐个结束容器中的 Activity. activity.finish()
参考网址: 2.6.2  随时随地退出程序

启动活动(p80):

给 Activity 添加一个封装优美的,静态的,清晰的启动方法。
步骤:

  1. 在 Activity 中定义一个静态的方法
1
2
3
4
5
6
7
private static String mArg0;
private static Object mArg1;
public static void startActivity(Activity context, String arg0, Object arg1){
mArg0 = arg0;
mArg1 = arg1;
context.startActivity( this.getClass() );
}
  1. 在需要启动 SecondActivity 的地方调用:
1
SecondActivity.startActivity(mContext, arg0, arg1);

第三章

从整体到局部,从构架到细节。

宽度,高度(p84)

match_parent:表示让当前控件的大小和父控件的大小一样
fill_parent:match_parent 的别名
wrap_content:使当前控件的大小正好包含住控件的内容(文字,图片 ) 等。

控件可见性(p98)

在 xml 布局中控制:

为控件添加属性(所有控件均支持),android:visibility="visible|invisible|gone"
visible 或者不写:可见,占空间
invisible:不可见,占空间(透明状态)
gone:不可见,不占空间

Java 代码中控制:

动态控制可见性:View.setVisibility(View.VISIBLE|View.INVISIBLE|View.GONE)
效果同 xml
另外,可以通过getVisibility()方法获取当前的控件状态,根据比较的结果,可以做一些特定的操作;

1
2
3
4
5
6
View view = findViewById(R.id.tv_main);
if(view.getVisibility() == View.GONE){
// TODO 如果视图是隐藏的,do:
} else if(view.getVisibility() == View.VISIBLE) {
// TODO 如果视图是显示的, do:
}

LinearLayout 重要属性说明(p105)

  • android:orientation=”vertical|horizontal”;控制 LinearLayout 的排列方向 (该属性用在 LinearLayout 本身上)
  • android:layout_gravity=”left|right|top|bottom|center_vertical|center_horizontal”;控件在父控件中的对齐方式。(该属性用在 LinearLayout 的子控件中)
  • android:layout_weight=”1”;通过比例的方式控制控件的大小。 (该属性用在 LinearLayout 的子控件中,剩余空间按比例分配。适配方案还得靠它)

易混淆的属性(p107)
android:layout_gravityandroid:gravity属性区别:

  1. android:layout_gravity属性:用于指定该控件在父控件中的对齐方式(可选值:top|bottom|left|right|center|center_vertical|center_horizontal
  2. android:gravity属性:用于指定控件中的内容在控件中的对齐方式( 可选值:top|bottom|left|right|center|center_vertical|center_horizontal
  3. 注意:layout_gravity,当父控件 LinearLayout 排列方向是 horizontal 时,
    只有垂直方向的对齐方式才会生效(bottom, top, center_vertical);
    当父控件 LinearLayout 排列方向是 vertical 时,只有水平方向的对齐方式才会生效(left,right,center_horizontal)

RelaviteLayout 布局

相对于父布局进行定位:

  • android:layout_alignParentTop=”true”
  • android:layout_alignParentBottom=”true”
  • android:layout_alignParentLeft=”true”
  • android:layout_alignParentRight=”true”
  • android:layout_centerInParent=”true”

相对于其他控件进行布局:

  • android:layout_above=”@id/view3”
  • android:layout_below=”@id/view3”
  • android:layout_toRightOf=”@id/view3”
  • android:layout_toLeftOf=”@id/view3”

相对于其他控件的边缘进行对齐显示:

  • android:layout_alignRight= “@id/view3”
  • android:layout_alignLeft= “@id/view3”
  • android:layout_alignTop= “@id/view3”
  • android:layout_alignBottom= “@id/view3”

其他:

  • android:layout_centerVertical=”true”
  • android:layout_centerHorizontal=”true”

注意 1:通过@id/tv_view引用的控件一定要先定义;而通过@+id/tv_view引用的控件也可以出现在后面。
注意 2:可以在一个控件中使用上面的一个或多个。例如,相对于父布局的“左上”布局,
就需要填写两个属性android:layout_alignParentTop="true";android:layout_alignParentLeft="true"

TableLayout 布局

  • TableLayout 中的每一个 TableRow 都表示表格中的一行,每一个子控件表示一列。
  • TableRow 中的控件不可以指定宽度。
  • 在 TableLayout 中添加属性:
    • android:stretchColumns="1",使指定的那一列拉伸,以自动适应屏幕的宽度。(0 表示第一列,1 表示第二列)
    • android:shrinkColumns="0",收缩指定的列;
    • android:collapseColumns="0",隐藏指定的列;

其他布局

  • FrameLayout 布局
    (所有控件默认摆放在左上角;多个子控件情况下下面的控件覆盖上面的控件;可通过 gravity 和 layout_gravity 来控制子控件的对齐方式)
  • AbsoluteLayout 布局(不推荐使用)

ListView 控件(p127)

需要的元素:

  1. 数据源
  2. 单个 item 的 layout 布局
  3. 适配器(简单的名称显示可继承自 ArrayAdapter,更丰富的布局可继承自 BaseAdapter)

**优化 **

  1. 利用 android 自带的缓存机制,convertView;
  2. 使用 ViewHolder 和 Tag;每一个 View 都可以存储一个 Tag 对象(看源码 mTag),对控件对应的实体进行缓存。当需要实体的时候,从 tag 中获取;
  3. 推荐阅读:【Android】通用系列 —— 用简单通用的方式操作 ListView

其他补充:

  • 为 listview 绑定适配器:lv.setAdapter(mAdapter);
  • 数据发生变化,要及时更新界面:mAdapter.notifyDataSetChanged();
  • 定位到某一项(即在屏幕上显示这一项):lv.setSelection(num);
  • 点击事件:setOnItemClickListener(OnItemClickListener ocl);
  • 给 ListView 项的分割线设置为透明色:android:divider=”@null”
  • 不显示滚动条:android:scrollbars="none"
  • 平滑滚动到指定条目:mLv.smoothScrollToPosition(pItem);
  • 设置 ListView 不可用:mLv.setEnable(false)
  • 设置 listView 不可滑动(可以通过拦截触摸事件,和mLv.setEnable(false)达到的效果不同):
1
2
3
4
5
6
7
8
public static void setListViewCannotSlide(ListView lv) {
lv.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return event.getAction() == MotionEvent.ACTION_MOVE;
}
});
}
  • 点击效果:设置 listSelector 和设置 item 的 layout 两种方式。(最好不要混用,推荐使用第二种方式)
  • 取消点击效果:andrdoi:listSelector="@android:color/transparent"
  • 动态取消点击效果:mLv.setSelector(new ColorDrawable(Color.TRANSPARENT));
  • 动态设置点击效果:mLv.setSelector(R.drawable.item_selector);

外部链接


第四章 Fragment 碎片

第一遍:不知道自己不知道。
第二遍:知道自己不知道,或不知道自己知道。
第三遍:知道自己知道。

简而言之,碎片(Fragment)是一种可以嵌入在活动当中的 UI 片段,它能让程序更加合理和充分地利用大屏幕空间。
学习资料:

要点难点:

  1. 通信–数据传递。
  2. 抽象成一个公用的类。
  3. 回退栈(remove,add,hide,show 等方法的区别)
  4. 屏幕适配:限定符;
  5. 双页单页模式(p182)

第五章 广播机制

注册广播:

1. 动态注册 (p190)

新建一个类,继承 BroadcastReceiver,重写父类的 onReceive 方法。
自定义的 MainActivity 中通过 Context 的registerReceiver(BroadCastReceiver bc,IntentFilter if)方法来注册 。
说明:其中参数 bc 是刚才新建类的实例,参数 if 是广播发送过来的 action 值包装成 IntentFilter 对象。

注意: 用完之后需要销毁,在 activity 的 onDestroy()方法中调用 unregisterReceiver()方法。
注意: 当访问的内容涉及到系统的关键性信息,那么需要在配置文件中配置相应的权限 permission ( http://developer.android.com/reference/android/Manifest.permission.html)。
注意: 应用程序发送的广播是可以被其他应用程序接收到的。跨进程通信。

2. 静态注册

  • 创建一个集成自 BroadCast 的类,并重写 onReceive()方法
  • 类似于 activity 的注册,使用标签,使用 android:name 来指定刚才创建的广播接收器(即实现了 BroadCast 的类的实例)
  • 标签中添加想要接收的广播。

另外: 使用静态注册可以达到开机启动的目的。静态注册在程序未启动的时候也可以接收广播。
注意: 监听某些广播也是需要声明权限的。

注意: 不要在 onReceive()中添加过多的逻辑或者任何的耗时操作,因为在广播接收器中是不允许开启线程的(如果运行时间较长,程序会报错)。
应用场合: 启动其他组件,例如创建一条状态栏通知,或启动一个服务等。

发送广播

标准广播
例如在 MainActivity 中点击按钮时发送广播:

1
2
Intent intent = new Intent("com.lyloou.broadcasttest.MY_BROADCAST");//还可以绑定一些数据到Intent中供接收者使用;
sendBroadcast(intent);

有序广播

  • 区别于标准广播的sendBroadcast(Intent intent), 有序广播是sendOrderedBroadcast(Intent intent, String receiverPermission )。 (可以参考 Context 的 API 文档看更详细内容)
  • 广播接收器的先后顺序,是通过在注册的时候设定的。例如,<intent-filter android:priority="100">
  • 有序广播是可以截断的。在 onRecevie()中调用abortBroadcast();方法将广播截断,后续的接收器就无法接收到该条广播。
    only works with broadcasts sent through Context.sendOrderedBroadcast.

本地广播(p202)

  • 区别于一般的广播,本地广播只允许本应用程序接收本应用程序发送的广播,解决了广播的安全性问题。
    其他程序的广播无法发送到我们程序的内部,更安全。比全局广播更高效。
  • 与一般的广播的动态注册方式类似。(一般广播,这里指的是全局广播)
  • 区别于一般广播的接收方式registerReceiver(),本地广播使用:
1
2
LocalBroadcastManager  localBroadCastManager =  LocalBroadcastManager.getInstance(Context c);
localBroadCastManager.registerReceiver(BroadcastReceiver br, Intent i);
  • 区别于一般广播的发送方式sendBroadcast(intent),本地广播使用:localBroadCastManager.sendBroadcast(intent)
  • 注意:也需要 unregister 进行注销处理。
  • 注意:本地广播无法通过静态方式注册接收, 静态注册在程序未启动的时候也可以接收广播,而本地广播肯定是在启动程序之后发送,所以不需要静态注册 。
  • 注意:不可以混用系统广播和本地广播,(例如通过 localBroadCastManager.registerReceiver 来注册蓝牙广播,是不对的,
    如果非得这样做,onReceiver 中将无法接收到蓝牙开启和关闭的广播)

其他

在广播接收器写一个弹框(p210)。
从广播接收器中启动活动需要注意的内容(p211):给 Intent 加入 FLAG_ACTIVITY_NEW_TASK 标识。


第六章 数据存储

持久化的方式

  • 文件存储:存储简单的文本数据和二进制数据。
  • SharePreference 存储 :存储的是键值对。
  • 数据库–SQLITE: 存储复杂关系型的数据。

文件存储(p221)

核心方法:Context 提供的 openFileInput()和 openFileOutput()方法;
注意:不适用于存储复杂类型的数据。

SharePreference 存储(p228)

  • 使用键值对的方式存储。

  • 使用 xml 格式来对数据进行管理。

  • 获取 SharePreference(下面三种方法不同之处在于参数和存储地址)

    1. Context 类中的 getSharedPreference(String filename, MODE_PRIVATE|MODE_MULTI_PROCESS),
      在系统中的具体表现:/data/data/<package name>/shared_prefs/filename参数名
    2. Activity 类中的 getPreference(MODE_PRIVATE|MODE_MULTI_PROCESS),文件名以当前活动的类名起名。
    3. PreferenceManager 类中的 getDefaultSharedPreference( MODE_PRIVATE|MODE_MULTI_PROCESS ),文件名以包名起名。
  • 保存数据步骤:

    1. 通过获取到的 SharePreference 对象实例 sp,引用其 edit()方法,SharedPreferences.Editor editor = sp.edit();
    2. editor.putInt(“age”,18);
    3. editor.putString(“name”,”Bob”);
    4. editor.commit();
  • 获取数据步骤:

    1. 同样首先获取到 SharePreference 对象实例 sp;
    2. int age = sp.getInt(“age”, 0);
    3. String name = sp.getString(“name”, “”);
    4. 获取到数据后的其他操作。

数据库(SQLITE-p238)

  • 创建一个 继承自 SQLiteOpenHelper 类的帮助类,重写 onCreate()和 onUpgrate()方法,在这两个方法中实现创建表和升级数据库的逻辑。
  • 通过帮助类获取实例:getReadableDatabase()和 getWritableDatabase()方法获取实例。两者的区别是前者在磁盘满的时候以只读方式打开数据库,而后者直接抛出异常。
  • 通过 db.execSQL()可以直接执行数据库语句。
  • 事务保证数据安全:进行读写操作,要使用事务(读数据就不需要事务了);
  • 数据库创建后,将不再重新创建,这意味着在客户端,onCreate()方法只执行一次。 除非卸载掉重新安装,
    如果是覆盖安装的话,修改了了数据库结构,就要通过onUpgrade()方法来控制了。

CRUD – 添加数据

利用 ContentValues 组件一组数据,通过 insert 来添加到数据库中。
ContentValues 利用键值的方式添加:,

1
2
3
4
ContentValues contentValues = new ContentValues();
contentValues.put("name", "think in java");
contentValues.put("version", 3);
db.insert("Book", null, contentValues);

CRUD – 更改数据

利用 ContentValues 组件一组数据,通过 update 来添加到数据库中。
ContentValues 利用键值的方式更新:,

1
2
3
ContentValues contentValues = new ContentValues();
contentValues.put("version", 4);
db.update("Book", contentValues, "name=?" , new String[]{"think in java"});

CRUD – 删除数据

获取到 db 实例后,即可执行删除操作

1
2
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "version < ?", new String[]{"4"});

CRUD – 查询数据

利用 db 的 query 方法来得到一个 Cursor 实例。对 Cursor 进行取值操作,即可得到想要的值。
cursor.moveToFirst(); 指针移动到第一行;
cursor.moveToNext(); 指针移动到下一个;

1
2
3
4
5
Cursor cursor = db.query("Book", null , null , null , null , null , null );
cursor.moveToFirst();
Log.d("LOU",cursor.getString(cursor.getColumnIndex( "name" )));
Log.d("LOU",cursor.getInt( cursor.getColumnIndex( "version" )) );
cursor.close();

技巧: 利用 switch 来优化数据库升级(p264)。


第七章 内容提供器(p268)

首先作为一个客户端程序员,来使用 API,接着创建自己的 API;

  • 内容提供器主要用于在不同的应用程序之间实现数据共享,目前内容提供器是 Android 实现跨程序共享数据的标准方式。

  • ContentProvider 比起文件和 SharedPreference 的 MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE 两种操作模式要更安全可靠。
    因为你只提供想要给其他程序访问的数据,其他数据就不会有任何影响。

  • ContentResolver 对象获取方式:‘’以通过 Context 的 getContentResolver()来获取。

  • ContentResolver 提供的一些方法进行 CRUD 操作。
    (基本类似于数据库的 CRUD,最明显的区别就是:
    数据库中的 CRUD 方法传入的参数是 表名,而 ContentResolver 中的传入的参数是 Uri
    对应的继承自 ContentProvider 的自定义内容提供器里面的 CRUD 方法设置的参数也是 Uri。)

  • 内容提供器的用法有两种:

    1. 使用现有的提供器来读取和操作某程序的数据。(作为客户端)
    2. 自己创建 ContentProvider 给自己的程序或其他程序提供外部访问接口,以供其读取和操作数据。(客户端和 API 端都有)
  • Uri:区别于 SQLiteDatabase 接受的参数table,ContentResolver 接受的参数是“URI”,
    要对某程序的数据进行读取和操作,需要制定其Uri

  • URI 格式:由两部分组成,权限 authority 和路径 path(p269) + 通配符的使用 (p277)

  • UriMatcher 的使用(p279)

  • Uri 的 getPathSegments()方法可以将字符串按照“/”分割成字符串列表,通过 get(0)这种方式访问列表中的字符串。

  • 所有内容提供者必须提供的方法(getType()),getType()方法的实现,用于获取 Uri 对象所对应的 MIME 类型;(p280)

  • 自定义的 ContentProvider 需要注册到 Manifest 中(p286)。

注意: 在每次创建内容提供者的时候,都要提醒自己,是不是真的需要这样做。
因为只有真正需要将数据共享出去的时候我们才应该创建内容提供器,
仅仅是用于内部程序访问的数据没必要使用内容提供者。

Lou: ContentProvider 扮演的是程序和程序之间数据传递的桥梁角色。
A 程序规定好自己的哪些数据内容可以访问,并设置好对应的 Uri,
B 程序借助 ContentProvider 提供器传入 A 程序规定好的 Uri 来读取和操作 A 程序的数据。
在 ContentProvider 中操作数据库,
就好像是 ContentProvider 提供了一个包装,
在其内根据传入的 Uri 来进行具体的数据库操作,
对外只需要提供一个接口即可(接口需要有正确的参数,例如 Uri、过滤条件等)。


第八章 多媒体

通知

使用步骤:

  1. 得到 manager:NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
  2. 得到 notification 实例:Notification notification = new Notification(R.drawable.ic_launcher, "This is ticker text", System.currentTimeMillis());
  3. 设置通知里的布局:notification.setLatesEventInfor(this, "This is content title", "This is content text", null);
    其中第四个参数是用来设置点击通知触发的逻辑,用 PendingIntent 来设定。
  4. 利用 manager 发出通知:manager.notify(1, notification);,其中 1 是该通知的唯一标志,可以用于之后的取消。

取消通知栏的通知

1
2
NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
manager.cancel(1); //其中1表示刚才设置的通知唯一标志。

通知栏点击事件处理

使用 PendingIntent(p302)

  • 被延迟执行的 Intent。(Intent 倾向于立即执行某个动作,PendingIntent 倾向于在某个合适的时机去执行某个动作)
  • 通过 getActivity(),或 getBroadCast(),或 getService()来获取 PendingIntent 实例,
    PendingIntent 结合第二个参数 Intent 来构建出“意图”,当被点击时执行相应的逻辑。 (p302) 1. 如果使用的是 getActivity(),那么就执行 startActivity(intent); 其中 intent:Intent intent = new Intent(getActivity(), MyActivity.class),指定其目标 Activity。 2. 如果使用的是 getBroadCast(),那么就调用 sendBroadCast(intent); 其中 intent:Intent intent = new Intent("com.example.broadcasttext.MY_BROADCAST"),指定其 action。 3. 如果使用的是 getService(),那么就调用 startService(intent);其中 intent:Intent intent = new Intent(getActivity(), MyService.class),指定目标 service。
  • 将得到的实例作为notification.setLatesEvenInfor()的第四个参数以实现点击通知的逻辑(启动一个 Activity、发送一条广播、启动某个服务等等 )。

通知的交互处理(p305)

  • 添加声音:
1
2
Uri soundUri = Uri.fromFile(new File("/system/media/audio/ringtones/Basic_tone.ogg"));
notification.sound = sounUri;
  • 添加振动:
1
2
3
long[] vibrates = {0, 1000, 1000, 1000};
notification.vibrate = vibrates;
// 注意:需要添加震动权限 <uses-permission android:name="android.permission.VIBRATE"/>
  • 添加 LED 通知
1
2
3
4
notification.ledARGB = Color.GREEN;
notification.ledOnMS = 1000;
notification.ledOffMS = 1000;
notification.flags = Notification.FLAG_SHOW_LIGHTS;
  • 设置默认通知类型:
1
notification.defaults = Notification.DEFAUT_ALL
  • 扩展:
    点亮屏幕

第九章 服务

多线程编程

子线程中不允许进行 UI 操作。

异步消息处理机制:

  • handler
  • AsyncTask
  • Activity.runOnUIThread(Runnable runnable)
  • IntentService

Handler(p348)

  1. 在主线程中创建 Handler 实例,并重写 handMessage()方法。
  2. 在子线程中通过 handler 发送消息(handler.sendMessage(msg);
  3. msg 会被添加到 MessageQueue 等待处理。
  4. Looper 不断的监测 MessageQueue 中是否有 Message,如果有那么就取出来分发到 handler 的 handleMessage()方法中处理。
  5. 在 handler.handleMessage()方法中进行 UI 操作。

AsyncTask(p349)

  1. AsyncTask 是一个抽象类,需要去继承才能用。

  2. 三个泛型参数

    • Params:执行 AsyncTask 任务需要的参数(例如进行网络操作的 URL 字符串);
    • Progress:后台执行任务的百分比;
    • Result:任务的执行完成后的返回值(例如 Bitmap)
  3. 常用的方法(重写)

    • onPreExecute(),任务执行之前的初始化操作(可以进行 UI 更新)。
    • doInBackground(Params…),耗时操作(不可用进行 UI 更新)
    • onProgressUpdate(Progress…),当在 doInBackground 方法里调用 publishProgres(Progress…)时会被调用,用于更新当前的 UI 状态。(可以进行 UI 更新)
    • onPostExecute(Result),当执行完 doInBackground()方法之后,返回 Result 值给 onPostExecute()方法。用于善后操作。(可以进行 UI 更新)
  4. 执行方式:new MyAsyncTask().execute();

服务

  • 间接继承自 Context
  • 不要因为服务是后台的概念,就把服务和线程搞混了,服务依然是在主线程中运行(即和活动共用一个线程)
    外部连接里有不错的解释:
    如果服务中用到耗时操作,依然需要使用子线程:
  • 外部连接:

既然在 Service 里也要创建一个子线程,那为什么不直接在 Activity 里创建呢?
这是因为 Activity 很难对 Thread 进行控制,当 Activity 被销毁之后,
就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。
而且在一个 Activity 中创建的子线程,另一个 Activity 无法对其进行操作。
但是 Service 就不同了,所有的 Activity 都可以与 Service 进行关联,
然后可以很方便地操作其中的方法,即使 Activity 被销毁了,之后只要重新与 Service 建立关联,
就又能够获取到原有的 Service 中 Binder 的实例。
因此,使用 Service 来处理后台任务,Activity 就可以放心地 finish,
完全不需要担心无法对后台任务进行控制的情况。

两种方式创建服务

  1. startService(serviceIntent);
  2. bindService(bindIntent, connection, BIND_AUTO_CREATE);
  • 我们完全有可能对一个服务既调用了 startService()方法,有调用了 bindService()方法。
    这种情况如何销毁服务?需要同时调用 stopService()(或者 stopself())和 unBindService()方法才行。(p364)
  • stopself()可以在 MyService 的任何位置调用,以停止服务。
  • stopService()跟 startService()一样,需要根据传入的参数来确定 stop 哪个服务。
1
2
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);

注意事项

  • 需要在 Manifest 中注册服务。
  • 通过 bindService 的方式创建的服务,可以更方便的控制它。
  • IntentService,一个异步的,可以自动停止的服务。(p367)。
    在重写的方法 onHandleIntent()里的进行耗时操作;

服务的应用

定时任务(Alarm)

外部连接


第十章 网络技术

使用 Http 协议访问网络(注意要添加网络权限)

HttpURLConnection(p380)

get 数据

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
private void sendRequestWithHttpURLConnection(){
new Thread(
@Override
public void run(){
HttpURLConnection connection = null;
try{
URL url = new URL("http://www.lyloou.com");
connection = (HttpURLConnection)url.openConnection();

// Get表示希望从服务器获取数据;Post表示希望提交数据给服务器
connection.setRequestMethod("GET");

// 设置连接超时
connection.setConnectionTimeout(8000);

// 设置读取超时
connection.setReadTimeout(8000);

// 获取输入流
InputStream in = connection.getInputStream();

// 对输入流进行读取操作
BufferReader reader = new BufferReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while((line=reader.readline()) != null){
response.append(line);
}

// 将结果返回
Message msg = new Message();
msg.what = 1;
msg.obj = response.toString();
handler.sendMessage(msg);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(connection != null){
connection.disconnect();
}
}
}
).start();
}

post 数据

1
2
3
// 获取输出流并写出数据。
DataOutputStream out = new DataOutputStream( connection.getOutputStream());
out.writeBytes("username=admin&password=123456");

HttpClient(p385 )

get 数据

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
private void sendRequestWithHttpClient(){
new Thread(
@Override
public void run(){
HttpClient httpClient = null;
try{
httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet("http://www.baidu.com");
HttpResponse httpResponse = httpClient.execute(httpGet);

if(httpResponse.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = httpResponse.getEntity();

// 将entity转换为字符串,并指定字符集编码
String response = EntityUtils.toString(entity, "utf-8");

// 将结果返回
Message msg = new Message();
msg.what = 1;
msg.obj = response.toString();
handler.sendMessage(msg);
}
} catch(Exception e) {
e.printStackTrace();
} finally {
if(client != null){
client.disconnect();
}
}
}
).start();
}

post 数据

1
2
3
4
5
6
7
8
9
HttpPost httpPost = new HttpPost(" http://www.baidu.com");

List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("username", "admin"));
params.add(new BasicNameValuePair("password", "123456"));

UrlEncodedFormEntity entity = new UrlEncodedFormEntity(param, "utf-8");
httpPost.setEntity(entity);
HttpResponse httpResponse = httpClient.execute(httpPost);

回调机制(p406,真牛)

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
public interface HttpCallbackListener{
void onFinish(String response);
void onError(Exception e);
}
private void sendRequest(final String address, final HttpCallbackListener listener){
new Thread(
@Override
public void run(){
HttpURLConnection connection = null;
try{
URL url = new URL( address );
connection = (HttpURLConnection)url.openConnection();

// Get表示希望从服务器获取数据;Post表示希望提交数据给服务器
connection.setRequestMethod("GET");

// 设置连接超时
connection.setConnectionTimeout(8000);

// 设置读取超时
connection.setReadTimeout(8000);

// 获取输入流
InputStream in = connection.getInputStream();

// 对输入流进行读取操作
BufferReader reader = new BufferReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while((line=reader.readline()) != null){
response.append(line);
}

// 将结果回调到参数中去处理,而不是return的方式返回数据(return的数据可能因为主线程先执行完而不能接受到,而通过参数就一定会执行了)。
if( listener != null){
listener.onFinish(response.toString());
}
} catch(Exception e) {
if( listener != null){
listener.onError(e);
}
} finally {
if(connection != null){
connection.disconnect();
}
}
}
).start();
}

XML 解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<apps>
<app>
<id>1</id>
<name>Google Maps</name>
<version>1.0</version>
</app>

<app>
<id>2</id>
<name>Chrome</name>
<version>1.0</version>
</app>

<app>
<id>3</id>
<name>Google Play</name>
<version>2.3</version>
</app>

</apps>

PULL 方式(p391 )

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
private void parseXMLWithPull(String xmlData){
try{
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
xmlPullParser.setInput(new StringReader(xmlData));
int eventType = xmlPullParser.getEventType();
String id = "";
String name = "";
String version = "";

while (eventType != XmlPullParser.END_DOCUMENT) {
String nodename = xmlPullParser.getName();
switch(eventType) {
case XmlPullParser.START_TAG:{
if("id".equals(nodename)){
id = xmlPullParser.nextText();
} else if("name".equals(nodename)){
name = xmlPullParser.nextText();
} else if("version".equals(nodename)){
version = xmlPullParser.nextText();
}
break;
}

case XmlPullParser.END_TAG:{
if("app".equals(nodename)){
Log.d("MainActivity", "id is " + id);
Log.d("MainActivity", "name is " + name);
Log.d("MainActivity", "version is " + version);
}
break;
}
default:
break;
}
eventType = xmlPullParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}

SAX 方式(p394)

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
private void parseXMLWithSAX(String xmlData){
try{
SAXParserFactory factory = SAXParserFactory.newInstance();
XMLReader xmlReader = factory.newSAXParser().getXMLReader();
ContentHandler contentHandler = new ContentHandler();

xmlReader.setContentHandler(contentHandler);
xmlReader.parse(new InputSource(new StringReader(xmlData)));
} catch (Exception e) {
e.printStackTrace();
}
}
// ......
public class ContentHandler extend DefaultHandler{
private String nodeName;
private StringBuilder id;
private StringBuilder name;
private StringBuilder version;

// startDocument会在开始XML解析的时候调用
@Override
public void startDocument() throws SAXException{
id = new StringBuilder();
name = new StringBuilder();
version = new StringBuilder();
}

// startElement会在解析某个节点的时候调用
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException{
nodeName = localName;
}

// characters会在获取节点内容的时候调用,可能会调用多次,需要对澶蠕动char进行控制
@Override
public void characters(char[] ch, int start, int length) throws SAXException{
if("id".equals(nodeName)){
id.append(ch, start, length);
} else if("name".equals(nodeName)){
name.append(ch, start, length);
} else if("version".equals(nodeName)){
version.append(ch, start, length);
}
}

// endElement会在完成解析某个节点的时候调用
@Override
public void endElement(String uri, String localName, String qName) throws SAXException{
if("app".equals(localName)){
Log.d("ContentHandler", "id is " + id.toString().trim());
Log.d("ContentHandler", "name is " + name.toString().trim());
Log.d("ContentHandler", "version is " + version.toString().trim());

// 清空还原数据
id.setLength(0);
name.setLength(0);
version.setLength(0);
}
}

// endDocument会在完成解析XML的时候调用
@Override
public void endDocument() throws SAXException{
Log.d("ContentHandler", "completed!!!");
}
}

扩展:DOM 方式

解析 JSON

1
2
3
4
5
[
{ "id": "5", "version": "5.5", "name": "Angry Birds" },
{ "id": "6", "version": "7.0", "name": "Clash of Clans" },
{ "id": "7", "version": "3.5", "name": "Hey Day" }
]

JSONObject 方式(p399)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void parseJSONWithJSONObject(String jsonData){
try{
JSONArray jsonArray = new JSONArray(jsonData);
for (int i = 0; i<jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);

String id = jsonObject.getString("id");
String version = jsonObject.getString("version");
String name = jsonObject.getString("name");

Log.d("MainActivity", "id is " + id);
Log.d("MainActivity", "version is " + version);
Log.d("MainActivity", "name is " + name);
}
} catch (Exception e) {
e.printStackTrace();
}
}

GSON 方式(p401)

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
class App{
private int id;
private String name;
private String version;

public void setId(int id){
this.id = id;
}
public void setName(String name){
this.name = name;
}
public void setVersion(String version){
this.version = version;
}
public int getId(){
return this.id;
}
public String getName(){
return this.name;
}
public String getVersion(){
return this.version;
}
}

//......
private void parseJSONWithGSON(String jsonData){
Gson gson = new Gson();
List<App> appList = gson.fromJson(jsonData, new TypeToken<List<App>>(){}.getType());

for (App app : appList) {
Log.d("MainActivity", "id is " + app.getId());
Log.d("MainActivity", "name is " + app.getName());
Log.d("MainActivity", "version is " + app.getVersion());
}
}

扩展:Jackson 方式,FastJSON 方式

摘要:

这里介绍了个人的 AutoHotKey 配置,包括它的便利性、用法说明等。在文章最后提供配置代码;

简单介绍

1
只要是可以编辑的地方,都用像vim一样的规则控制光标。

当我有了这样的想法时,我找到了Autohotkey

使用 Autohotkey 带来其他便利:

1
2
3
4
* 一次按键,在浏览器中用指定搜索引擎搜索选中的文字。
* 一次按键,打开指定的某一个软件或同时打开几个软件。(开机后,一个按键打开所有必备软件)
* 一次按键,可以输入自定义的模板:例如输入`.dta`,就能达到输入`2017-01-11 18:57:55`的效果。
* 一次按键,快速输入特殊符号(●、★、×、√ 等等)。

举例说明

光标控制

1
2
3
4
5
6
7
8
9
10
* 光标左移一次:Alt+h
* 光标右移一次:Alt+l
* 光标上移一次:Alt+,
* 光标下移一次:Alt+i
* 光标置于行首:Alt+0
* 光标置于行尾:Alt+4
* 选中光标位置到行首的文字:Shift+Alt+0
* 选中光标位置到行尾的文字:Shift+Alt+4
* 删除右侧一个字符(同`Delete`按键):Alt+'
* 无论光标在当前行何处,新起一行:Shift+Enter

搜索

1
* 选中文字,用谷歌搜索:alt+g

导航标签

1
2
3
* 下一个标签:Alt+k
* 上一个标签:Alt+j
* 新建标签:Alt+t

控制窗口
通过MoveWindows.ahk文件来启用。

1
2
* 按住alt键,左键拖拽窗口任意地方可以移动窗口;(非最大化模式)
* 按住alt键,右键拖拽窗口,可以调整窗口的对象;(非最大化模式)

小技巧

通过#Include, MoveWindows.ahk,这种方式可以加载配置文件。
这样就不用将所有文件都写在一个配置文件,而是放在不同的文件中,然后通过#Include, MoveWindows.ahk来加载。
模块化管理,更容易变更。(有些设置好像不能生效,让我又改回去了)

配置代码

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#Include, Plugins/MoveWindows.ahk

#!p::suspend ;挂起所有autohotkey按键
#!o::Edit ;打开配置文件
Capslock::Enter ;大小写按键替换成Enter键

;LWin & WheelUp::Send ^#{left}
;LWin & WheelDown::Send ^#{right}

#m::WinMinimize, A ; 最小化当前窗口
#`::WinMinimize, A ; 最小化当前窗口

; 启动指定的bat文件(可以在bat文件中启动其他程序)
#a::run win_a
#c::run win_c
#g::run win_g
#q::run win_q
#s::run win_s
#t::run win_t
#w::run win_w

#n::run notepad

;光标方向控制
!i::Send {up} ;光标上移
!,::Send {down} ;光标下移
!h::Send {left} ;光标左移
!l::Send {right} ;光标右移
!4::Send, {end} ;到行末
!0::Send, {home} ;到行首



;编辑区域操作
Shift & enter::send {end}{enter} ;下起一行
+^!h::send,+^{left} ;选中左移一个单词
+^!l::send,+^{right} ;选中右移一个单词
+!i::send,{shiftdown}{up} ;选中上移
+!,::send,{shiftdown}{down} ;选中下移
+!h::send,{shiftdown}{left} ;选中左移
+!l::send,{shiftdown}{right} ;选中右移
+!4::send,+{end} ;选中当前光标位置到行末
+!0::send,+{home} ;选中当前光标位置到行首

;复制当前行到剪切板
+!c::
send,{home}{shiftdown}{end}{ShiftUp}
Send,^c
Send, {end}
Return



;替换按键
!n::Send ^n ;新建
!w::Send ^w ;关闭
!v::Send ^v ;粘贴
!x::Send ^x ;剪切
!c::Send ^c ;复制
!s::Send ^s ;保存
!'::Send {delete} ;删除光标后面的一个字母或汉字

;特殊符号
;「
![::
clipboard = 「
send ^v
return

;
!]::
clipboard = 」
send ^v
return

;时间输入
;如:14:19:59
::.tt::
d = %A_Hour%:%A_Min%:%A_Sec%
clipboard = %d%
Send ^v
return

;如:2016.01.16
::.dd::
d = %A_YYYY%.%A_MM%.%A_DD%
clipboard = %d%
Send ^v
return

;如:2016-05-08 17:09:10
::.dta::
d = %A_YYYY%-%A_MM%-%A_DD% %A_Hour%:%A_Min%:%A_Sec%
clipboard = %d%
Send ^v
return

;如:[01.06 14:00]
::.dt::
d = [%A_MM%.%A_DD% %A_Hour%:%A_Min%]
clipboard = %d%
Send ^v
return


;用google搜索
!g::
Send ^c
Run http://www.google.com/search?q=%clipboard%
return


;for chrome
#IfWinActive ahk_class Chrome_WidgetWin_1
!j::Send ^+{Tab}
!k::Send ^{Tab}
!t::Send ^t
return

注意

!!! 如果要通过剪切板输出中文,不要将配置文件设置为 UTF-8,而是要设置为 GBK(坑了我好长时间呢)

参考

0%