編輯:關於Android編程
2015年的Google IO大會上,Android 團隊發布了一個數據綁定框架(Data Binding Library),官方原生支持 MVVM 模型。以後可以直接在 layout 布局 xml 文件中綁定數據了,無需再 findViewById 然後手工設置數據了。其語法和使用方式和 JSP 中的 EL 表達式非常類似。
Data Binding Library 是一個 support 庫,支持 Android 2.1+ 版本 (API level 7+)。 由於該框架需要使用編譯器來生成很多代碼,所以需要配合新版本的 Android Studio (1.3.0-beta1 + 版本)才能使用,Gradle 插件1.5.0-alpha1以上。
例:
Android Studio版本是1.3+,更新Support repository到最新的版本。
新建一個project,在dependencies中添加以下依賴
classpath "com.android.databinding:dataBinder:1.0-rc1"
新建module,並且在module的build.gradle文件中添加
apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'
Studio1.5+忽略以上兩點,只需在對應module的build.gradle中添加
android {
....
dataBinding {
enabled = true
}
}
ps:即使依賴的庫中使用了data binding,該module也必須在build.gradle中配置
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}
Alternatively, you can get the view via:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
在 ListView 或者 RecyclerView 的Adapter中使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
例如,如果你的數據對象中有兩個方法:
public class MyHandlers {
public void onClickFriend(View view) { ... }
public void onClickEnemy(View view) { ... }
}
…
像java一樣,java.lang.*
自動導入,可以直接使用
當存在同名的類時,其中一個可以使用別名
導入類還可以用於在表達式中引用靜態屬性和方法
…
自動生成的binding類會為每個聲明的變量生成setter和getter方法,在調用setter方法之前變量的值為java默認值。
當表達式中需要使用時,會自動生成一個名為“context”的變量,“context”的值是由根布局的getContext()獲得,如果手動聲明了一個名為“context”的變量,默認的會被覆蓋。
默認Binding類會根據布局文件名自動生成,放在module包下的databinding包中,如布局文件contact_item.xml
會自動生成ContactItemBinding,如果module包名是com.example.my.app
,這個類將被放在com.example.my.app.databinding
下。通過class屬性,可以修改Binding類的類名和所在包。
databinding包下,類名為ContactItem
...
module包下
...
指定包名
...
使用include時,需要將變量傳遞到被包含的布局中,此時name.xml
和contact.xml
中必須有user變量。
Data binding不支持
下直接的子元素使用include,比如下面的就不支持:
以下表達式和java的一樣
* 算術表達式 + - / * %
* 字符串連接 +
* 邏輯運算符 && ||
* 位運算符 & | ^
* 一元運算符 + - ! ~
* 位移運算符 >> >>> <<
* 關系運算符 == > < >= <=
* instanceof
* 分組 ()
* 字面值 - 字符, 字符串, 數字, null
* 類型轉換
* 方法調用
* 訪問屬性
* 訪問數組 []
* 三元運算符 ?:
例:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
java中可以使用而這裡不能使用的
* this
* super
* new
* Explicit generic invocation
當表達式左邊不為null時使用左邊的值,如果為null使用右邊的
android:text="@{user.displayName ?? user.lastName}"
等效於
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
生成的data binding代碼會自動檢查空值,避免空指針。例如表達式@{user.name}
,如果user
是null
,user.name
將使用默認值null
,user.age
將使用默認值0。
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
android:text='@{map["firstName"]}'
android:text="@{map[`firstName`}"
android:text="@{map["firstName"]}"
直接在表達式中使用resources
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
Format strings and plurals may be evaluated by providing parameters:
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
When a plural takes multiple parameters, all parameters should be passed:
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
Some resources require explicit type evaluation.
任何一個簡單的Java對象(POJO)都可以用來綁定,但是修改一個POJO不能夠觸發UI更新。
以下是3種數據改變通知機制,Observable objects, observable fields, 和 observable collections,當這3種數據對象綁定到UI,並且數據改變時UI會自動更新
實現 android.databinding.Observable
接口。為了方便,Android提供了基類BaseObservable
,實現了監聽器的注冊機制。
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
Bindable
注解編譯時在 BR
中生成一個entry。 BR是在編譯時生成在module的一個類,功能與R.java類似。
屬性較少時可以使用ObservableField
,包含ObservableBoolean
, ObservableByte
, ObservableChar
, ObservableShort
, ObservableInt
, ObservableLong
, ObservableFloat
, ObservableDouble
, 和 ObservableParcelable
,簡單的POJO就可以實現。
private static class User {
public final ObservableField firstName =
new ObservableField<>();
public final ObservableField lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
訪問值時使用:
user.firstName.set("Google");
int age = user.age.get();
ObservableArrayMap user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
…
ObservableArrayList
…
生成的binding類引用了layout中的View,就如前面說的,Binding的類名和包名都可以定制,生成的binding類都繼承自ViewDataBinding
。
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
If the layout was inflated using a different mechanism, it may be bound separately:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
如果布局中某個View設置了id,則生成的binding類中會包含:
public final TextView firstName;
public final TextView lastName;
binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
ViewStubBinding binding = DataBindingUtil.bind(inflated);
User user = new User("fee", "lang");
binding.setUser(user);
}
});
以 RecyclerView 為例,Adapter 的 DataBinding 需要動態生成,因此我們可以在 onCreateViewHolder 的時候創建這個 DataBinding,然後在 onBindViewHolder 中獲取這個 DataBinding。
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
當一個變量改變時,binding會在下一幀時改變UI,需要立刻執行,可以使用executePendingBindings()
方法。
當一個綁定的值改變時,可以指定調用哪個方法來設置值
沒有配置自定義屬性,data binding會自動找到對應的setAttribute
方法(命名空間無所謂),注意表達式的返回值,必要的時候進行類型轉換。
如果不想調用根據名字匹配的setter,可以通過BindingMethods
注解重新匹配。
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
通常開發者不需要重命名setters
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
還可以接收多個參數
@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
這個適配器會在imageUrl和error都在ImageView中設置,imageUrl是字符串,error時drawable時調用
Binding adapter methods may optionally take the old values in their handlers. A method taking old and new values should have all old values for the attributes come first, followed by the new values:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
if (oldPadding != newPadding) {
view.setPadding(newPadding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
}
事件適配器必須使用帶有一個抽象方法的接口或抽象類,如:
@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
View.OnLayoutChangeListener newValue) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (oldValue != null) {
view.removeOnLayoutChangeListener(oldValue);
}
if (newValue != null) {
view.addOnLayoutChangeListener(newValue);
}
}
}
當一個listener有多個方法時,必須分成多個listener。比如View.OnAttachStateChangeListener
有兩個方法:onViewAttachedToWindow()
和 onViewDetachedFromWindow()
,就必須創建兩個接口來區分他們。
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}
因為改變一個listener經常會影響另一個,所以我們需要有3個不同的綁定適配器。
@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
setListener(view, null, attached);
}
@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
setListener(view, detached, null);
}
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
final OnViewAttachedToWindow attach) {
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
final OnAttachStateChangeListener newListener;
if (detach == null && attach == null) {
newListener = null;
} else {
newListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if (attach != null) {
attach.onViewAttachedToWindow(v);
}
}
@Override
public void onViewDetachedFromWindow(View v) {
if (detach != null) {
detach.onViewDetachedFromWindow(v);
}
}
};
}
final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
newListener, R.id.onAttachStateChangeListener);
if (oldListener != null) {
view.removeOnAttachStateChangeListener(oldListener);
}
if (newListener != null) {
view.addOnAttachStateChangeListener(newListener);
}
}
}
The above example is slightly more complicated than normal because View uses add and remove for the listener instead of a set method for View.OnAttachStateChangeListener. The android.databinding.adapters.ListenerUtil class helps keep track of the previous listeners so that they may be removed in the Binding Adaper.
By annotating the interfaces OnViewDetachedFromWindow
and OnViewAttachedToWindow
with @TargetApi(VERSION_CODES.HONEYCOMB_MR1)
, the data binding code generator knows that the listener should only be generated when running on Honeycomb MR1 and new devices, the same version supported by addOnAttachStateChangeListener(View.OnAttachStateChangeListener).
當binding表達式返回一個對象時,會從automatic, renamed, 和 custom setters 中選擇一個,這個對象會被轉換成選擇的setter的參數類型。
這方便了用ObservableMaps保存數據,例如:
userMap
返回了一個 Object ,這個 Object 將會自動轉換成 setText(CharSequence)
的參數類型。當參數類型不明確時,開發者需要在表達式中進行轉換。
有時特殊的類型需要自動轉換。比如,當設置背景時:
這裡,背景是一個 Drawable
,但是顏色是一個整數,需要把 int
轉換成 ColorDrawable
,可以使用帶有 BindingConversion
注解的一個靜態方法:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
Note:不要將上面的和以下混淆
Dialog 使用總結Dialog 是Android中對話框相關的類,起到相關提示的作用。Dialog在Android 中主要分為以下幾類:AlertDialog :警告
本篇隨筆將講解一下Android當中比較常用的兩個布局容器--ScrollView和HorizontalScrollView,從字面意義上來看也是非常的簡單的,Scrol
一加3怎麼刷入第三方recovery?一加3最近挺熱門的,很多人買了手機回來第一時間也是要刷各種第三方rom如CM,那麼一加3怎麼刷入第三方recovery
方法一:重寫TextView的onDraw方法,也挺直觀就是不太好控制顯示完圖片後再顯示字體所占空間的位置關系。一般如果字體是在圖片上重疊的推薦這樣寫。時間關系,這個不付