本项目旨在用来展示一个学习的样本,而不是用于生产环境,如果要用于生产的话,我建议使用React Native。Kratos展示了如何利用Annotation Processor和Javapoet在编译时期生成代码,达到类似Lisp宏一样的基础效果(实际差得远),实现Annotation free;还展示了利用Kolin语言的Delegate,实现检测变量的变化;最重要的是,展示了一套利用Json来渲染View的思路,并在Json中做配置,利用反射实现数据的View的绑定。这些代码,有助于帮助你理解React Native,比如Kotlin对应着Js,Json配置文件作为外部DSL对应着Jsx。
其实本想着实现一个用Lisp在Android上开发的框架,无奈本人太菜,算了,以后再说,先研究React Native好了 ╮(╯▽╰)╭
Provide basic Double Binding(Data Binding) feature on Android.
- Using annotation to generate boilerplate code.
- Bind view and data to help you clean your code.
The following code demostrate that two views(EditText and TextView) bound to one single data(which in the code boundData
holds the data. you can later access or change the data by using boundData.get()
and boundData.set("some string")
)
public class SimpleActivity extends Activity {
@BindText({R.id.test_doublebinding_input, R.id.test_doublebinding_presenter})
KString boundData = new KString();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
Kratos.bind(this);
}
}
The presenter(TextView) will behave exactly the same as input(EditText) since they were bound to the same data:
What if you want yourself a custom update behavior than the default update method when data changes? You can add custom update function to update view:
@BindText(R.id.some_edittext_id)
KString boundData = new KString(new KString.Update() {
@Override
public void update(@NotNull View view, @NotNull String s) {
EditText et = (EditText)view; //Add custom update function here.
int start = et.getSelectionStart(); // This code is the same as: public KString boundData1 = new KString(),
et.setText(s); // since this custom function is the same as the default function.
et.setSelection(start);
}
});
If you are using Kotlin, the code is more simple:
@KBindText("some_edittext_id")
public var boundData = KString {
it, new ->
it as EditText
val position = this.selectionStart
this.setText(new)
this.setSelection(position)
}
The concept of Card is very important in Kratos.
Basically, every view is a card, each activity is generated by one single json file, a little bit like html.
The goal is to implement each view as a card, and then easily reuse them by manipulating json file.
For example, the following activity is generated from sample.json
file.
You can easily handle click event by override onEventMainThread
method.
- Kratos uses toolbar by default, so you need a
NoActionBar
theme in your style file, or you can just useKratosTheme
. - When you run into non-final id problem in your library project, just use annotation start with "L", for example
LBindText
. But remember that you should never use both kind of annotation in the same file!
- No more fear of the changing requirements. Since activities are generated from json file, you can manipulate the layout on the fly(imagine put the json file on a server, like html on website).
- No more redundant code. Since each view is a card, you can reuse views easily by manipulate the json file.
- Easily create and compose custom views. All you need to do is to extend KCard, create you own KData and bind the data to the card(must write full package name, so that kratos can find your card). You can implement rather complex card using kratos depending on your skills. For example, all the view in the following activity are card:
For more code see kratos-sample.
To Create a card consists of two TextView that can also handle click even2
-
Create a class that extends KData, for example:
public class KText extends KData { public KString text1; public KString text2; }
-
Create a class that extends KCard, use KData as its Generic, for example:
@BindLayout(R.layout.kcard_text) //@LBindLayout("kcard_text") @Binds({@Bind(id = R.id.kcard_text_text1, data = "text1"), @Bind(id = R.id.kcard_text_text2, data = "text2")}) public class TextCard extends KCard<KText> { public TextCard(@NotNull Context context) { super(context); } @Override public void init() { setOnLinkListener(); } }
You should initailize your stuff inside
init
function, not constructor;Notice that it uses
BindLayout
to specify its layout:<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="https://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ffffff" android:gravity="center_horizontal" android:orientation="vertical" android:padding="16dp"> <TextView android:id="@+id/kcard_text_text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:textColor="#888888" android:textSize="14sp" /> <TextView android:id="@+id/kcard_text_text2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:textColor="#888888" android:textSize="14sp" /> </LinearLayout>
-
Create a Activity that extends KCardActivity:
public class CardSampleActivity extends KCardActivity { private void showToast(String text) { Toast.makeText(CardSampleActivity.this, text, Toast.LENGTH_SHORT).show(); } @Override public void onEventMainThread(@NotNull KOnClickEvent<KData> event) { super.onEventMainThread(event); switch (event.id) { case "textCard1": showToast("Handle click on textCard1!"); break; } } }
Notice that it handles click event by overriding o
nEventMainThread
method. -
Pass your json layout to your activity:
{ "header": { "title": "Card Sample" }, "body": [ { "data": { "text": "this is text1", "text": "this is text2"� }, "type": "me.ele.kratos_sample.TextCard", "id": "textCard1", "style": { "margin_top": 20, "margin_left": 20, "margin_right": 20 } } ] }
Notice that it uses full package name in
type
property.You can use kratos' util function to pass json file to your next activity:
ActivityUtils.jump(SimpleActivity.this, CardSampleActivity.class, CODE_CARD_SAMPLE, R.raw.sample);
-
Create a package-info.java file in your source folder like this:
@PackageName package me.ele.kratos_sample; import kratos.PackageName;
You will get something like this:
If you want a custom update behavior when data been changed, add a function annotated with OnKStringChanged
to your card, like this:
@OnKStringChanged("text1")
public void updateText1(@NotNull TextView v, @NotNull String s) {
v.setText(s);
Log.d("TextCard", "custom updater!");
}
You may say:"That's no big deal. what's the point?"
But
Assume you have this data object which implements Parcelable
:
public class Customer implements Parcelable {
public KString name = new KString();
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(this.name, flags);
}
public Customer() {
}
protected Customer(Parcel in) {
this.name = in.readParcelable(KString.class.getClassLoader());
}
public static final Creator<Customer> CREATOR = new Creator<Customer>() {
public Customer createFromParcel(Parcel source) {
return new Customer(source);
}
public Customer[] newArray(int size) {
return new Customer[size];
}
};
}
This Customer has a property -- name, and this is what you do.
- Change the
text1
element in the json file above to this:
"text1": "{Customer.name}",
- Add this code to your activity:
Customer customer;
@Override
public void onFinishRender() {
bind(this, customer);
}
Tada!!From now on, if you want to change the view that has Customer's name in it, all you need to do is change the value of customer.name
, say you do this:
customer.name.set("Merlin");
This is what you get:
Dude, Holy ****, This is Magic..
Kratos is still under development and a lot of features haven't been added to it yet. But the basic idea is here. If you are interested in this project, feel free to fork.
Kratos is available from maven central:
apply plugin: 'com.neenbedankt.android-apt'
buildscript {
ext.kratos_version = '0.2.4'
repositories {
mavenCentral()
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
dependencies {
compile "com.github.acemerlin:kratos:$kratos_version"
apt "com.github.acemerlin:kratos-compiler:$kratos_version"
}
- Use Kotlin's Observable Delegate to listen to changes made to certain property.
- Use Kotlin's Extension feature to add functions to View.
- Use Annotation Processor to generate code which binds the data and the view(or views).