butterknife
http://jakewharton.github.io/butterknife/
Annotate fields with @BindView and a view ID for Butter Knife to find and automatically cast the corresponding view in your layout.
class ExampleActivity extends Activity {
@BindView(R.id.title) TextView title;
@BindView(R.id.subtitle) TextView subtitle;
@BindView(R.id.footer) TextView footer;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
}
Instead of slow reflection, code is generated to perform the view look-ups. Calling bind delegates to this generated code that you can see and debug.
The generated code for the above example is roughly equivalent to the following:
public void bind(ExampleActivity activity) {
activity.subtitle = (android.widget.TextView) activity.findViewById(2130968578);
activity.footer = (android.widget.TextView) activity.findViewById(2130968579);
activity.title = (android.widget.TextView) activity.findViewById(2130968577);
}
RESOURCE BINDING
Bind pre-defined resources with @BindBool, @BindColor, @BindDimen, @BindDrawable, @BindInt, @BindString, which binds an R.bool ID (or your specified type) to its corresponding field.
class ExampleActivity extends Activity {
@BindString(R.string.title) String title;
@BindDrawable(R.drawable.graphic) Drawable graphic;
@BindColor(R.color.red) int red; // int or ColorStateList field
@BindDimen(R.dimen.spacer) Float spacer; // int (for pixel size) or float (for exact value) field
// ...
}
NON-ACTIVITY BINDING
You can also perform binding on arbitrary objects by supplying your own view root.
public class FancyFragment extends Fragment {
@BindView(R.id.button1) Button button1;
@BindView(R.id.button2) Button button2;
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}
}
Another use is simplifying the view holder pattern inside of a list adapter.
public class MyAdapter extends BaseAdapter {
@Override public View getView(int position, View view, ViewGroup parent) {
ViewHolder holder;
if (view != null) {
holder = (ViewHolder) view.getTag();
} else {
view = inflater.inflate(R.layout.whatever, parent, false);
holder = new ViewHolder(view);
view.setTag(holder);
}
holder.name.setText("John Doe");
// etc...
return view;
}
static class ViewHolder {
@BindView(R.id.title) TextView name;
@BindView(R.id.job_title) TextView jobTitle;
public ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
}
You can see this implementation in action in the provided sample.
Calls to ButterKnife.bind can be made anywhere you would otherwise put findViewById calls.
Other provided binding APIs:
Bind arbitrary objects using an activity as the view root. If you use a pattern like MVC you can bind the controller using its activity with ButterKnife.bind(this, activity).
Bind a view's children into fields using ButterKnife.bind(this). If you use <merge> tags in a layout and inflate in a custom view constructor you can call this immediately after. Alternatively, custom view types inflated from XML can use it in the onFinishInflate() callback.
VIEW LISTS
You can group multiple views into a List or array.
@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name })
List<EditText> nameViews;
The apply method allows you to act on all the views in a list at once.
ButterKnife.apply(nameViews, DISABLE);
ButterKnife.apply(nameViews, ENABLED, false);
Action and Setter interfaces allow specifying simple behavior.
static final ButterKnife.Action<View> DISABLE = new ButterKnife.Action<View>() {
@Override public void apply(View view, int index) {
view.setEnabled(false);
}
};
static final ButterKnife.Setter<View, Boolean> ENABLED = new ButterKnife.Setter<View, Boolean>() {
@Override public void set(View view, Boolean value, int index) {
view.setEnabled(value);
}
};
An Android Property can also be used with the apply method.
ButterKnife.apply(nameViews, View.ALPHA, 0.0f);
LISTENER BINDING
Listeners can also automatically be configured onto methods.
@OnClick(R.id.submit)
public void submit(View view) {
// TODO submit data to server...
}
All arguments to the listener method are optional.
@OnClick(R.id.submit)
public void submit() {
// TODO submit data to server...
}
Define a specific type and it will automatically be cast.
@OnClick(R.id.submit)
public void sayHi(Button button) {
button.setText("Hello!");
}
Specify multiple IDs in a single binding for common event handling.
@OnClick({ R.id.door1, R.id.door2, R.id.door3 })
public void pickDoor(DoorView door) {
if (door.hasPrizeBehind()) {
Toast.makeText(this, "You win!", LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Try again", LENGTH_SHORT).show();
}
}
Custom views can bind to their own listeners by not specifying an ID.
public class FancyButton extends Button {
@OnClick
public void onClick() {
// TODO do something!
}
}
BINDING RESET
Fragments have a different view lifecycle than activities. When binding a fragment in onCreateView, set the views to null in onDestroyView. Butter Knife returns an Unbinder instance when you call bind to do this for you. Call its unbind method in the appropriate lifecycle callback.
public class FancyFragment extends Fragment {
@BindView(R.id.button1) Button button1;
@BindView(R.id.button2) Button button2;
private Unbinder unbinder;
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
unbinder = ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}
@Override public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}
OPTIONAL BINDINGS
By default, both @Bind and listener bindings are required. An exception will be thrown if the target view cannot be found.
To suppress this behavior and create an optional binding, add a @Nullable annotation to fields or the @Optional annotation to methods.
Note: Any annotation named @Nullable can be used for fields. It is encouraged to use the @Nullable annotation from Android's "support-annotations" library.
@Nullable @BindView(R.id.might_not_be_there) TextView mightNotBeThere;
@Optional @OnClick(R.id.maybe_missing) void onMaybeMissingClicked() {
// TODO ...
}
MULTI-METHOD LISTENERS
Method annotations whose corresponding listener has multiple callbacks can be used to bind to any one of them. Each annotation has a default callback that it binds to. Specify an alternate using the callback parameter.
@OnItemSelected(R.id.list_view)
void onItemSelected(int position) {
// TODO ...
}
@OnItemSelected(value = R.id.maybe_missing, callback = NOTHING_SELECTED)
void onNothingSelected() {
// TODO ...
}
BONUS
Also included are findById methods which simplify code that still has to find views on a View, Activity, or Dialog. It uses generics to infer the return type and automatically performs the cast.
View view = LayoutInflater.from(context).inflate(R.layout.thing, null);
TextView firstName = ButterKnife.findById(view, R.id.first_name);
TextView lastName = ButterKnife.findById(view, R.id.last_name);
ImageView photo = ButterKnife.findById(view, R.id.photo);
Add a static import for ButterKnife.findById and enjoy even more fun.
Download
GRADLE
compile 'com.jakewharton:butterknife:(insert latest version)'
annotationProcessor 'com.jakewharton:butterknife-compiler:(insert latest version)'
License
Copyright 2013 Jake Wharton
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
8 個最優(yōu)秀的 Android Studio 插件
Android Studio是目前Google官方設計的用于原生Android應用程序開發(fā)的IDE?;贘etBrains的IntelliJ IDEA,這是Google I/O 2013第一個宣布的作為Eclipse的繼承者,深受廣大Android社區(qū)的歡迎。在經(jīng)過漫長的測試階段后,最終版本于去年12月發(fā)布。
Android Studio是一個功能全面的開發(fā)環(huán)境,裝備了為各種設備——從智能手表到汽車——開發(fā)Android應用程序所需要的所有功能。不但總是有改進的余地,Android Studio還提供了對第三方插件的支持,下面本文將列出一些最有用的插件。
- H.A.X.M(硬件加速執(zhí)行管理器)
如果你想使用Android模擬器更快地執(zhí)行應用程序,那么H.A.X.M是你的最佳選擇。H.A.X.M提供Android SDK模擬器在英特爾系統(tǒng)中的硬件加速。我認為H.A.X.M是最有用的插件,因為它能讓Android開發(fā)人員盡快地在模擬器上運行最新的Android版本。
安裝H.A.X.M
打開Android SDK管理器,選擇“Intel x86 Emulator Accelerator (HAXM installer)”,接受許可并安裝軟件包。
HAXM Install
這個進程只是下載軟件包,還沒有安裝。為了完成安裝到圖片所示的SDK路徑C:\Users\Administrator\AppData\Local\Android\sdk
(安裝在Windows機器上)并找到下載的文件夾。我的是:C:\Users\Administrator\AppData\Local\Android\sdk\extras\intel
. 打開安裝文件Hardware_Accelerated_Execution_Manager,單擊可執(zhí)行的intelhaxm-android,繼續(xù)安裝。完成此安裝后,你就可以使用該模擬器了。
HAXM exe -
genymotion
Genymotion是測試Android應用程序,使你能夠運行Android定制版本的旗艦工具。它是為了VirtualBox內(nèi)部的執(zhí)行而創(chuàng)建的,并配備了一整套與虛擬Android環(huán)境交互所需的傳感器和功能。使用Genymotion能讓你在多種虛擬開發(fā)設備上測試Android應用程序,并且它的模擬器比默認模擬器要快很多。
Genymotion
如果你想要確保你開發(fā)的應用程序能夠在所有支持的設備上流暢地運行,但在特定設備上排除錯誤有困難時,那就應該好好利用這款偉大的插件。
想要安裝Genymotion,可以參見以前發(fā)布過的教程。 - Android Drawable Importer
為了適應所有Android屏幕的大小和密度,每個Android項目都會包含drawable文件夾。任何具備Android開發(fā)經(jīng)驗的開發(fā)人員都知道,為了支持所有的屏幕尺寸,你必須給每個屏幕類型導入不同的畫板。Android Drawable Importer插件能讓這項工作變得更容易。它可以減少導入縮放圖像到Android項目所需的工作量。Android Drawable Importer添加了一個在不同分辨率導入畫板或縮放指定圖像到定義分辨率的選項。這個插件加速了開發(fā)人員的畫板工作。
Drawable add
Import Drawables
安裝Android Drawable Importer
Drawable-Importer - Android ButterKnife Zelezny
Android ButterKnife是一個“Android視圖注入庫”。它提供了一個更好的代碼視圖,使之更具可讀性。 ButterKnife能讓你專注于邏輯,而不是膠合代碼用于查找視圖或增加偵聽器。用ButterKnife編程,你必須對任意對象進行注入,注入形式是這樣的:
@InjectView(R.id.title) TextView title;
Android ButterKnife Zelezny是一款Android Studio插件,用于在活動、片段和適配器中,從所選的XML布局文件生成ButterKnife注入。該插件提供了生成XML對象注入的最快方式。如果只是一兩個注入,那么這樣寫是沒有問題的,但如果你有很多要寫,那就需要參考所有的注入,將它們編寫到源文件中。
下面是一個代碼在使用Android ButterKnife之前的樣子的例子:
Code Before
以及使用之后:
Code After
安裝ButterKnife Zelezny:
ButterKnife-Zelezny - Android Holo Colors Generator
開發(fā)Android應用程序需要偉大的設計和布局。Android Holo Colors Generator則是定制符合喜好的Android應用程序的最簡單方法。Android Holo Colors Generator是一個允許你為你的應用程序隨心所欲地創(chuàng)建Android布局組件的插件。此插件會生成所有必要的可在項目中使用的相關(guān)的XML畫板和樣式資源。
安裝 Holo Colors Generator:
Holo-Colors-Generator - Robotium Recorder
Robotium Recorder是一個自動化測試框架,用于測試在模擬器和Android設備上原生的和混合的移動應用程序。Robotium Recorder可以讓你記錄測試案例和用戶操作。你也可以查看不同Android活動時的系統(tǒng)功能和用戶測試場景。
Robotium Recorder能讓你看到當你的應用程序運行在設備上時,它是否能按預期工作,或者是否能對用戶動作做出正確的回應。如果你想要開發(fā)穩(wěn)定的Android應用程序,那么此插件對于進行徹底的測試很有幫助。
下面是一個例子,是我的應用程序使用Robotium Recorder時的樣子:
Robotium example
想要安裝Robotium Recorder,請登錄它的官方頁面,并根據(jù)你的操作系統(tǒng)的版本在安裝區(qū)域選擇Robotium Recorder。
7.jimu Mirror
Android Studio配備了一個可視化的布局編輯器。但是一個靜態(tài)的布局預覽有時候?qū)τ陂_發(fā)人員而言可能還不夠,因為靜態(tài)預覽不能預覽動畫、顏色和觸摸區(qū)域,所以jimu Mirror來了,這是一個可以讓你在真實的設備上迅速測試布局的插件。jimu Mirror允許在設備上預覽隨同編碼更新的Android布局。
安裝jimu Mirror:
jimu-Mirror
8.Strings-xml-tools
Strings-xml-tools是一個雖小但很有用的插件,可以用來管理Android項目中的字符串資源。它提供了排序Android本地文件和添加缺少的字符串的基本操作。雖然這個插件是有限制的,但如果應用程序有大量的字符串資源,那這個插件就非常有用了。
安裝Android Strings.xml tools:
Android-Strings.xml-tools
您有更優(yōu)秀的Android Studio插件嗎,歡迎在留言中告訴我們。
譯文鏈接:http://www.codeceo.com/article/8-android-studio-plugins.html英文原文:The Top 8 Plugins for Android Studio
一、簡介:
Kotlin 是一個基于 JVM 的新的編程語言,由 JetBrains 開發(fā)。JetBrains,作為目前廣受歡迎的 Java IDE IntelliJ 的提供商,在 Apache 許可下已經(jīng)開源其Kotlin 編程語言。
可以理解為類似于iOS的Swift。
二、特性:
輕量級:這一點對于Android來說非常重要。項目所需要的庫應該盡可能的小。Android對于方法數(shù)量有嚴格的限制,Kotlin只額外增加了大約6000個方法。
互操作:Kotlin可與Java語言無縫通信。這意味著我們可以在Kotlin代碼中使用任何已有的Java庫;因此,即便這門語言還很年輕,但卻已經(jīng)可以使用成百上千的庫了。除此之外,Kotlin代碼還可以為Java代碼所用,這意味著我們可以使用這兩種語言來構(gòu)建軟件。你可以使用 Kotlin開發(fā)新特性,同時使用Java實現(xiàn)代碼基的其他部分。
強類型:我們很少需要在代碼中指定類型,因為編譯器可以在絕大多數(shù)情況下推斷出變量或是函數(shù)返回值的類型。這樣就能獲得兩個好處:簡潔與安全。
Null安全:Java最大的一個問題就是null。如果沒有對變量或是參數(shù)進行null判斷,那么程序當中就有可能拋出大量的 NullPointerException,然而在編碼時這些又是難以檢測到的。Kotlin使用了顯式的null,這會強制我們在必要時進行null檢查。
三、Android Studio中的配置
注意:
Android Studio是Intellij IDEA的插件實現(xiàn),Intellij IDEA是由JetBrains開發(fā),Kotlin 就是JetBrains創(chuàng)造的。所以,要想使用Kotlin,你必須先使用起來Android Stduio。
1、安裝插件 選擇這里的Kotlin相關(guān)的插件安裝,有些文檔中介紹有2個插件,其實目前這一個包含另一個了,所以安裝一個就行,安裝完之后會要求你重新打開Android Studio。

2、重啟完Android Studio之后在任意一個包下右鍵New , 會發(fā)現(xiàn)多了一個"Kotlin File/Class" 和 "Kotlin Activity"
3、"Kotlin File/Class"即 Kotlin類或者文件
"Kotlin Activity"即 Kotlin的Activity類
4、試著建一個"Kotlin File/Class" 文件

發(fā)現(xiàn)右上角有一個配置選項“Configure” , 默認第一次使用都需要配置一下

選擇對所有modules配置還是對指定的配置
選擇OK后,會跳到build.gradle文件下,你會發(fā)現(xiàn)app下的build.gradle和根目錄下的build.gradle文件都會出現(xiàn)變化
注意黃色背景部分,沒有的自己手動添加上去。
根目錄下的build.gradle:

buildscript { ext.kotlin_version = '1.1.2-4' ext.support_version = '23.1.1' ext.anko_version = '0.8.2' repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }}allprojects { repositories { jcenter() }}task clean(type: Delete) { delete rootProject.buildDir}

app目錄下的build.gradle:

apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'android { compileSdkVersion 25 buildToolsVersion "25.0.3" defaultConfig { applicationId "com.xqx.xautoviewgroup" minSdkVersion 15 targetSdkVersion 23 versionCode 1 versionName "1.0" } lintOptions { abortOnError false } buildTypes { debug { // 顯示Log buildConfigField "boolean", "LOG_DEBUG", "true" versionNameSuffix "-debug" minifyEnabled false zipAlignEnabled false shrinkResources false signingConfig signingConfigs.debug } release { // 不顯示Log buildConfigField "boolean", "LOG_DEBUG", "false" //混淆 minifyEnabled true //Zipalign優(yōu)化 zipAlignEnabled true // 移除無用的resource文件 shrinkResources true //前一部分代表系統(tǒng)默認的android程序的混淆文件,該文件已經(jīng)包含了基本的混淆聲明,后一個文件是自己的定義混淆文件 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main.java.srcDirs += 'src/main/kotlin' }}dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support:design:25.3.1' compile 'com.android.support:support-v4:25.3.1' compile 'com.github.bumptech.glide:glide:3.6.1' compile 'com.jph.takephoto:takephoto_library:4.0.3' compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.anko:anko-common:$anko_version"}repositories { mavenCentral()}

接下來就可以進行Kotlin的編碼實戰(zhàn)了。
淺談Kotlin(一):簡介及Android Studio中配置
淺談Kotlin(二):基本類型、基本語法、代碼風格
使用Kotlin&Anko, 扔掉XML開發(fā)Android應用
嘗鮮使用Kotlin寫了一段時間Android。說大幅度的減少了Java代碼一點不夸張。用Java的時候動不動就new一個OnClickListener()匿名類,動不動就類型轉(zhuǎn)換的地方都可以省下很多。更不用說特殊的地方使用data class更是少些不知道多少代碼。
Jetbrains給Android帶來的不僅是Kotlin,還有Anko。從Anko的官方說明來看這是一個雄心勃勃的要代替XML寫Layout的新的開發(fā)方式。Anko最重要的一點是引入了DSL(Domain Specific Language)的方式開發(fā)Android界面布局。當然,本質(zhì)是代碼實現(xiàn)布局。不過使用Anko完全不用經(jīng)歷Java純代碼寫Android的痛苦。因為本身是來自Kotlin的,所以自然的使用這種方式開發(fā)就具有了:
類型安全,不再需要那么多的findById()之后的類型轉(zhuǎn)換。
null安全,Kotlin里,如果一個變量用?表示為可空,并且使用?之后再調(diào)用的時候,即使變量為空也不會引發(fā)異常。
無需設備解析XML,因為Anko本質(zhì)是代碼實現(xiàn)的界面和布局,所以省去了這些麻煩。
代碼復用,可以通過繼承AnkoComponent的方式實現(xiàn)代碼復用。XML布局是每一個Activity,每一個View各自專屬一個,
代碼復用比較少。
來一個列子看一下。為了不太墨跡,一些不必要的xml聲明此處略去。
<RelativeLayout>
<TextView
android:id="@+id/sample_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="Sample text view"
android:textSize="25sp" />
<Button
android:id="@+id/sample_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/sample_text_view"
android:text="Sample button" />
</RelativeLayout>
relativeLayout {
val textView = textView("Sample text view") {
textSize = 25f
}.lparams {
width = matchParent
alignParentTop()
}
button("Sample button").lparams {
width = matchParent
below(textView)
}
}
準備工作
首先,安裝一個Kotlin的插件是必須的。有了這個插件才可以使用Kotlin,然后才可以使用Anko。安裝這個插件和Android Studio里安裝別的插件市一樣的。只需要使用Kotlin查找就可以找到,之后安裝即可。
在build.gradle里添加下面的代碼:
dependencies {
compile 'org.jetbrains.anko:anko-sdk15:0.8.3' // sdk19, sdk21, sdk23 are also available
compile 'org.jetbrains.anko:anko-support-v4:0.8.3' // In case you need support-v4 bindings
compile 'org.jetbrains.anko:anko-appcompat-v7:0.8.3' // For appcompat-v7 bindings
}
然后sync一把。配置的問題解決。
寫一個ListView熱身
首先創(chuàng)建一個ListView的item點擊之后跳轉(zhuǎn)的activity。這里叫做TabDemo1。
現(xiàn)在就創(chuàng)建這個listview,并在listview的item點擊之后調(diào)轉(zhuǎn)到相應的activity去。
這個listview非常簡單,只在一個豎直的布局中放置,并且寬度和高度都是填滿豎直
布局。
// 1
verticalLayout {
padding = dip(16)
// 2
val list = listView() {
// 3
adapter = ArrayAdapter<String>(this@MainActivity, android.R.layout.simple_list_item_1, items)
// 4
onItemClickListener = object : AdapterView.OnItemClickListener {
override fun onItemClick(parent: AdapterView<*>?, v: View?, position: Int, id: Long) {
when (position) {
0 -> {
// 5
startActivity<TabDemo1>()
}
}
}
}
}.lparams(width = matchParent) { // 6
height = matchParent
}
}
分別解釋:
豎直布局。本質(zhì)是LinearLayout,并且orientation的值為vertical。但是
水平方向的就沒有vetialLayout這種可以直接使用的了,需要自己寫明orientation。
創(chuàng)建一個listview。
給這個listview添加adapter。這里簡單實用ArrayAdapter<String>。
添加OnItemClickListener。object : AdapterView.OnItemClickListener用來
創(chuàng)建實現(xiàn)某個接口的匿名類。
startActivity<TabDemo1>(),是Anko的語法糖。startActivity(SourceActivity.this, DestActivity.class)
可以直接簡化為startActivity<DestActivity>()。簡單了不少。
在lparams中設置layout params相關(guān)的內(nèi)容。默認的都是wrap content。這個設置為
寬、高都為match parent。
用Fragment寫一個Tab布局
熱身結(jié)束。我們來開始真正的開發(fā)階段。
下面要開發(fā)的是一個日記App。一共有三個tab,第一個是日記列表,第二個tab是寫日記,第三個tab可以設置一些字體大小等(這里只用來占位,不做實現(xiàn))。
每一個tab都用一個Fragment來展示內(nèi)容。這三個tab分別HomeListFragment, DetailFragment,DiarySettingsFragment。這個三個fragment都在一個叫做TabDemo1的托管Activity里。
現(xiàn)在就從這個托管activity:TabDemo1開始。這里我們不使用默認的ActionBar,而是用完全自定義的方式來寫一個我們自己的action bar。所以需要把界面設定為全屏模式。設置全屏的模式的方法有很多,我們用設置style的方式來實現(xiàn)。
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.Light.NoActionBar">
</style>
之后把這個style應用在activity在AndroidManifest.xml配置中。
這個時候這個托管activity的界面布局就是一個完全的白板了。這個白板現(xiàn)在要分為上中下三部分。上部為我們自定義的action bar,最下面的是tab bar,剩下的部分就是每個tab的內(nèi)容的fragment。
我們來看一下這個布局應該怎么寫:
// 1
relativeLayout {
id = ID_RELATIVELAYOUT
backgroundColor = Color.LTGRAY
// 2
linearLayout {
id = ID_TOP_BAR
backgroundColor = ContextCompat.getColor(ctx, R.color.colorPrimary)
orientation = LinearLayout.HORIZONTAL
titleTextView = textView {
text = "Some Title"
textSize = 16f
textColor = Color.WHITE
gravity = Gravity.CENTER_HORIZONTAL or Gravity.CENTER_VERTICAL
}.lparams {
width = dip(0)
height = matchParent
weight = 1f
}
}.lparams {
width = matchParent
height = dip(50)
alignParentTop()
}
// 3
linearLayout {
id = ID_BOTTOM_TAB_BAR
orientation = LinearLayout.HORIZONTAL
backgroundColor = Color.WHITE
// 4
homeListTab = weightTextView {
text = "List"
normalDrawable = resources.getDrawable(R.mipmap.tab_my_normal)
selectedDrawable = resources.getDrawable(R.mipmap.tab_my_pressed)
onClick { tabClick(0) }
}
detailTab = weightTextView {
text = "Detail"
normalDrawable = resources.getDrawable(R.mipmap.tab_channel_normal)
selectedDrawable = resources.getDrawable(R.mipmap.tab_channel_pressed)
onClick { tabClick(1) }
}
settingsTab = weightTextView {
text = "Settings"
normalDrawable = resources.getDrawable(R.mipmap.tab_better_normal)
selectedDrawable = resources.getDrawable(R.mipmap.tab_better_pressed)
onClick { tabClick(2) }
}
}.style { // 5
view ->
when (view) {
is TextView -> {
view.padding = dip(5)
view.compoundDrawablePadding = dip(3)
view.textSize = 10f
view.gravity = Gravity.CENTER
}
else -> {
}
}
}.lparams {
height = dip(50)
width = matchParent
alignParentBottom()
}
// 6
fragmentContainer = frameLayout {
id = ID_FRAMELAYOUT
backgroundColor = Color.GREEN
}.lparams {
below(ID_TOP_BAR)
above(ID_BOTTOM_TAB_BAR)
width = matchParent
height = matchParent
}
}
前文的例子用了一個verticalLayout, 這里用的是relativeLayout的布局。
這里是自定義action bar。使用換一個linearLayout。如前所述,要橫向布局linear layout
就需要單獨的指定orientation:orientation =LinearLayout.HORIZONTAL。這里比較簡單,只有一個顯示title的text view。
這里需要注意gravity = Gravity.CENTER_HORIZONTAL or Gravity.CENTER_VERTICAL
可以直接寫成gravity = Gravity.CENTER。這里是為了突出or的用法。Kotlin里的or
就是java的|操作符的作用。
這部分的布局是tab bar。
這里用的是weightTextView而不是textView。后面會詳細的講解這一部分。
給tab bar添加style。此style不是彼style。這個style,會遍歷tab bar的linear layout內(nèi)部的全部的view,然后根據(jù)when表達式匹配對應的規(guī)則,之后給對應于規(guī)則的view設置相應的屬性。比如,這里會用when語句查看view是否為textView,如果是的話就給這個view設置padding、drawable padding、text size以及gravity屬性。tab bar的linear layout有三個text view,所以他們都會被設置這些屬性。
每一個tab的內(nèi)容展示用fragment就是這里了。準確的說是fragment的container。
這個container是一個framelayout。在action bar之下,在tab bar之上。在布局的時候有below(ID_TOP_BAR), above(ID_BOTTOM_TAB_BAR)。ID_TOP_BAR和ID_BOTTOM_TAB_BAR就分別是action bar和tab bar的id值。這些id值自由設定。
另外,在java寫的時候常用的findViewById()方法在Kotlin和Anko中可以改為的find<FrameLayout>(ID_FRAMELAYOUT)。不見得簡單,但是增加了類型安全。不用再強制類型轉(zhuǎn)換。也不用擔心相關(guān)的錯誤再發(fā)生。
上文第4點用到了weightTextView。這是一個自定義的view。在Anko布局中,可以根據(jù)自己的需要自定義各種各樣的view。但是,需要經(jīng)過一個小小的處理之后才可以使用到Anko的布局中。這個小小的處理就叫做擴展。下面看看如何給Anko添加weightTextView擴展的。
首先自定義一個view:WeightTextView。
class WeightTextView(context: Context) : TextView(context) {
var normalDrawable: Drawable? = null
var selectedDrawable: Drawable? = null
init {
var layoutParams = LinearLayout.LayoutParams(dip(50),
LinearLayout.LayoutParams.MATCH_PARENT, 1f)
layoutParams.weight = 1f
this.layoutParams = layoutParams
}
override fun setSelected(selected: Boolean) {
super.setSelected(selected)
if (selected) {
this.backgroundColor = ContextCompat.getColor(context, R.color.textGray)
this.textColor = ContextCompat.getColor(context, R.color.textYellow)
if (selectedDrawable != null) {
this.setCompoundDrawablesWithIntrinsicBounds(null, selectedDrawable, null, null)
}
} else {
this.backgroundColor = ContextCompat.getColor(context, android.R.color.transparent)
this.textColor = ContextCompat.getColor(context, R.color.textGray)
if (normalDrawable != null) {
this.setCompoundDrawablesWithIntrinsicBounds(null, normalDrawable, null, null)
}
}
}
}
附加解釋:
方法setSelected()是被迫添加的。在使用Anko,相當于使用代碼開發(fā)Android布局的時候selector不起作用。只好把點擊后的高亮效果寫在自定義的text view里。
下面看看如何擴展Anko,來使用我們上面的自定義view。
public inline fun ViewManager.weightTextView() = weightTextView {}
public inline fun ViewManager.weightTextView(init: WeightTextView.() -> Unit) = ankoView({ WeightTextView(it) }, init)
這部分涉及到的語法內(nèi)容可以參考官網(wǎng)。
這里簡單介紹一下。拿官網(wǎng)的例子說一下:
class HTML {
fun body() { ... }
}
現(xiàn)在有這么一個HTML類,那么調(diào)用的時候可以這樣:
html {
body()
}
在這么一個lambda表達式里就可以直接這樣調(diào)用HTML類的方法了,中間的過程是怎么樣的呢
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // create the receiver object
html.init()
return html
}
其實灰常的簡單呢。在方法html()里,參數(shù)是一個HTML類的擴展方法,并且此方法無參,返回Unit(java的void)。
在方法執(zhí)行的過程中,首先初始化了HTML。之后調(diào)用了這個作為參數(shù)傳入的擴展方法。在具體調(diào)用html()方法的時候,可以只簡單寫一個lambda表達式作為傳入的HTML擴展方法。既然是一個類的擴展方法,那當然可以調(diào)用這個類內(nèi)部的方法了。
為了幫助理解,這里給出一個參數(shù)是方法的方法:
fun main(args: Array<String>) {
calling("yo") { p ->
println("method called $p")
}
calling("yoyo", ::called)
}
fun calling(param: String, func: (String) -> Unit) {
func(param)
}
fun called(p: String) {
println("output string $p")
}
第一個是用lambda表達式作為傳入方法,第二個是已經(jīng)定義好的一個方法作為傳入方法。
Fragment的處理
本文中的重點在于使用Anko做布局,具體的邏輯處理java寫和Kotlin寫沒有什么區(qū)別。這里只簡單介紹一下。
為了保證兼容,這里使用Support v4來處理Fragment的顯示等操作。在activity的一開始就把需要的fragemnt都加載進來。
fun prepareTabFragments() {
val fm = supportFragmentManager
homeListFragment = HomeListFragment.newInstance()
fm.beginTransaction()
.add(ID_FRAMELAYOUT, homeListFragment)
.commit()
detailFragment = DetailFragment.newInstance(null)
detailFragment?.modelChangeListener = homeListFragment
fm.beginTransaction()
.add(ID_FRAMELAYOUT, detailFragment)
.commit()
settingsFragment = DiarySettingsFragment.newInstance()
fm.beginTransaction()
.add(ID_FRAMELAYOUT, settingsFragment)
.commit()
}
每一個tab項被點擊的時候的處理:
fun tabClick(index: Int) {
info("index is $index")
val ft = supportFragmentManager.beginTransaction()
ft.hide(homeListFragment)
ft.hide(detailFragment)
ft.hide(settingsFragment)
// unselect all textviews
homeListTab?.isSelected = false
detailTab?.isSelected = false
settingsTab?.isSelected = false
when (index) {
0 -> {
homeListTab?.isSelected = true
ft.show(homeListFragment)
}
1 -> {
detailTab?.isSelected = true
ft.show(detailFragment)
}
2 -> {
settingsTab?.isSelected = true
ft.show(settingsFragment)
}
else -> {
}
}
ft.commit()
}
分別開始每一個Fragment
在開始之前需要考慮一個很嚴重的事情:數(shù)據(jù)存在什么地方。本來應該是SQLite或者存在云上的。存在云裳就可以實現(xiàn)同一個賬號登錄在任何地方都可以同步到同樣的內(nèi)容。這里只簡單模擬,存放在app的內(nèi)存里。存放在Application派生類AnkoApplication的
靜態(tài)屬性diaryDataSource里。diaryDataSource是一個ArrayList一樣的列表。
class AnkoApplication : Application() {
override fun onCreate() {
super.onCreate()
}
companion object {
var diaryDataSource = mutableListOf<DiaryModel>()
}
}
第一個tab,HomeListFragment
HomeListFragment類作為第一個tab內(nèi)容展示fragment,用來顯示全部的日記列表的布局就非常簡單了,和我們前面的例子沒有什么太大的差別。就是在一個verticalLayout里放一個list view。這個list view的data source只需要一個列表。
// 1
var view = with(ctx) {
verticalLayout {
backgroundColor = Color.WHITE
listView = listView {
adapter = ArrayAdapter<DiaryModel>(ctx,
android.R.layout.simple_list_item_1,
AnkoApplication.diaryDataSource)
onItemClick { adapterView, view, i, l ->
toast("clicked index: $i, content: ${AnkoApplication.diaryDataSource[i].toString()}")
}
}
// 2
emptyTextView = textView {
text = resources.getString(R.string.list_view_empty)
textSize = 30f
gravity = Gravity.CENTER
}.lparams {
width = matchParent
height = matchParent
}
}
}
// 3
listView?.emptyView = emptyTextView
return view
在activity里的布局可以直接寫vertical{},但是在fragment里不可以這樣。直接寫vertical{}就已經(jīng)把這個layout添加到父view上了,這fragment里是不行的。在fragment里需要創(chuàng)建一個單獨的view,并返回。用with語句來創(chuàng)建這樣一個單獨的view。
在vertial layout里添加了一個textview。
上面一步創(chuàng)建的textview作為list view沒有數(shù)據(jù)的時候顯示的empty view來使用。
第二個tab,DetailFragment
日記的內(nèi)容包括,日記title,日記本身的內(nèi)容還有日記的日期。
所以布局上就包括日記的title、內(nèi)容輸入用的EditText以及為了說明用的text view,還有edit text里的hint。最后還有一個選擇
日期的控件。
return with(ctx) {
verticalLayout {
padding = dip(10)
backgroundColor = Color.WHITE
textView("TITLE") {
}.lparams(width = matchParent)
titleEditText = editText {
hint = currentDateString()
lines = 1
}.lparams(width = matchParent) {
topMargin = dip(5)
}
textView("CONTENT") {
}.lparams(width = matchParent) {
topMargin = dip(15)
}
contentEditText = editText {
hint = "what's going on..."
setHorizontallyScrolling(false)
}.lparams(width = matchParent) {
// height = matchParent
topMargin = dip(5)
}
button(R.string.button_select_time) {
gravity = Gravity.CENTER
onClick {
val fm = activity.supportFragmentManager
var datePicker = DatePickerFragment.newInstance(diaryModel?.date)
datePicker.setTargetFragment(this@DetailFragment, DetailFragment.REQUEST_DATE)
datePicker.show(fm, "date")
}
}
// *
button(R.string.button_detail_ok) {
onClick {
v ->
println("ok button clicked")
try {
var model = diaryModel!!
model.title = titleEditText?.text.toString()
model.content = contentEditText?.text.toString()
AnkoApplication.diaryDataSource.add(model)
modelChangeListener?.modelChanged()
toast(R.string.model_saved_ok)
} catch(e: Exception) {
Log.d("##DetailFragment", "error: ${e.toString()}")
toast(R.string.model_save_error)
}
}
}.lparams {
topMargin = dip(10)
width = matchParent
}
}.style {
view ->
when (view) {
is Button -> {
view.gravity = Gravity.CENTER
}
is TextView -> {
view.gravity = Gravity.LEFT
view.textSize = 20f
view.textColor = Color.DKGRAY
}
}
}
}
需要注意打星號的地方。按鈕在點擊之后會彈出一個dialog fragment來顯示日期view。用戶可以在這個日期view里選擇相應的日期。但是,如何從日期dialog fragment傳遞選擇的日期給DetailFragment呢?這里就涉及到兩個fragment之間傳遞數(shù)據(jù)的問題。
選擇日期的dialog fragment是DatePickerFragment。
var pickerView = DatePicker(activity)
pickerView.calendarViewShown = false
pickerView.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
pickerView.init(year, month, day) {
view, year, month, day ->
mDate = GregorianCalendar(year, month, day).time
arguments.putSerializable(EXTRA_DATE, mDate)
}
return AlertDialog.Builder(activity)
.setView(pickerView)
.setTitle(R.string.date_picker_title)
.setPositiveButton(R.string.picker_button_ok) { dialog, which ->
toast("hello world!")
sendResult(Activity.RESULT_OK)
}.create()
首先DatePickerFragment要繼承DialogFragment之后override方法onCreateDialog(savedInstanceState: Bundle)。在這個方法里使用上面代碼創(chuàng)建一個包含日期選擇器的dialog。
在選擇日期的時候,會觸發(fā)DatePicker的OnDateChangedListener接口的onDateChanged方法。我們在這個方法里記錄選擇好的日期數(shù)據(jù),在dialog的positive按鈕點擊之后把這個數(shù)據(jù)發(fā)送給DetailFragment。
那么怎么發(fā)送呢?使用target fargment方法。在detail fragment彈出dialog fragment的時候,把detail fragment設置為target fragment。
button(R.string.button_select_time) {
gravity = Gravity.CENTER
onClick {
val fm = activity.supportFragmentManager
var datePicker = DatePickerFragment.newInstance(diaryModel?.date)
// *
datePicker.setTargetFragment(this@DetailFragment, DetailFragment.REQUEST_DATE)
datePicker.show(fm, "date")
}
}
在標星下面的一行代碼中。datePicker.setTargetFragment(this@DetailFragment,DetailFragment.REQUEST_DATE)將DetailFragment設定為target fragment,并且指定REQUEST_DATE這code,為以后取出數(shù)據(jù)使用。
companion object Factory {
val REQUEST_DATE = 0`
}
在positive按鈕點擊之后執(zhí)行方法sendResult回傳數(shù)據(jù)
private fun sendResult(resultCode: Int) {
if (targetFragment == null)
return
var i = Intent()
i.putExtra(EXTRA_DATE, mDate)
// *
targetFragment.onActivityResult(targetRequestCode, resultCode, i)
}
調(diào)用targetFragment的onActivityResult()方法來回傳日期數(shù)據(jù)。
在DetailFragment中通過override方法onActivityResult()來接收數(shù)據(jù)。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode != Activity.RESULT_OK) {
return
}
if (requestCode != REQUEST_DATE) {
return
}
var date = data?.getSerializableExtra(DatePickerFragment.EXTRA_DATE) as Date
diaryModel?.date = date
}
日期數(shù)據(jù)傳輸這部分到這里結(jié)束。
全文也可以在這里畫上一個句點了。以上還有很多關(guān)于Anko沒有使用的地方。Anko也是可以實現(xiàn)代碼界面分離的。繼承AnkoComponent可以寫出獨立的布局文件,并且可以用anko preview插件來預覽界面效果。就拿setting這個tab的fragment來舉例:
首先定義一個獨立的布局文件:
class SettingsUI<T> : AnkoComponent<T> {
override fun createView(ui: AnkoContext<T>) = with(ui) {
verticalLayout {
backgroundColor = ContextCompat.getColor(ctx, R.color.SnowWhite)
textView { text = resources.getString(R.string.settings_title) }
button("activity with the same `AnkoComponent`") {
id = ID_BUTTON
}
}
}
companion object Factory {
public val ID_BUTTON = 101
}
}
把這個布局文件用在DiarySettingsFragment上:
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
val view = SettingsUI<DiarySettingsFragment>().createView(AnkoContext.create(ctx, DiarySettingsFragment()))
return view
}
然后這個布局還可以用在我們剛剛創(chuàng)建的TempActivity上:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
SettingsUI<TempActivity>().setContentView(this)
val button = find<Button>(SettingsUI.ID_BUTTON)
button.text = "you are in `TempActivity`, CLICK!"
button.onClick {
toast("${TempActivity::class.java.simpleName}")
}
}
Activity上使用就簡單很多了,只需要這么一句SettingsUI<TempActivity>().setContentView(this)。
代碼在這里。除了布局Anko還有其他的一些語法糖糖也很是不錯,不過這里就不多說了。有更多想了解的,請移步官網(wǎng)。
對于 Android 開發(fā)者而言,Kotlin 有很多優(yōu)點。最明顯的是它的類型系統(tǒng)和對空類型的處理,這迫使你在編碼時指明哪些變量可為空,并在使用的時候遵循這個約定,之后編譯器就會介入并確保對變量的賦值都是有效的??罩羔槷惓3]是我在 Android 應用程序中處理的最常見的異常類型。Kotlin 有助于公平的競爭環(huán)境。
Kotlin 另外一個顯著的優(yōu)點是具備擴展函數(shù)[4]的能力,通過給 Context,Activity 和 Date 類添加擴展函數(shù),使得我的代碼簡潔了很多,同時變得更加易于閱讀。
使用Kotlin開發(fā)Android
Kotlin非常適合開發(fā)Android應用程序,因為它在沒有引入任何新的約束的情況下,將現(xiàn)代語言語言的所有優(yōu)點帶到Android平臺上:
兼容性:Kotlin完全兼容JDK 6,可以順利地確保Kotlin應用可以運行在更老的設備上。Kotlin工具在Android Studio中完全支持,且與Android構(gòu)建系統(tǒng)兼容。
性能:由于兩者非常相近得字節(jié)碼結(jié)構(gòu),Kotlin應用程序可以運行得和Java一樣快。隨著Kotlin對內(nèi)聯(lián)函數(shù)的支持,相同的代碼邏輯使用Lambads表達式比使用java的運行的更快。
互用性:Kotlin 100%可以和java互操作,這就允許Kotlin應用可以使用現(xiàn)有的Android庫。同時它還引入了注解處理,這樣數(shù)據(jù)綁定和Dagger也可以使用啦。
內(nèi)存消耗:Kotlin有一個非常簡潔的運行庫,它會進一步地減少ProGuard的使用。在 實際項目中,Kotlin程序的運行只不過是添加了數(shù)百個方法和少于100k的apk文件的大小。
編譯時間:Kotlin支持高效的增量編譯(incremental compilation),因此在清理構(gòu)建方面還需要額外的開銷,增量版本通常與Java一樣快或更快
學習曲線:對于Java開發(fā)者而言,上手Kotlin非常容易。內(nèi)置的Kotlin插件可以自動地完成從Java到Kotlin的轉(zhuǎn)換工作。另外,. Kotlin Koans 用一系列的可交互的練習,為我們掌握Kotlin語言的關(guān)鍵特征提供了指導。












