Kratos

本项目旨在用来展示一个学习的样本,而不是用于生产环境,如果要用于生产的话,我建议使用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好了 ╮(╯▽╰)╭

Logo

Provide basic Double Binding(Data Binding) feature on Android.

Data Binding

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:

Example

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)
}

Card

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.

Card

You can easily handle click event by override onEventMainThread method.

Notice that

What's the benefit

ComplexCard

For more code see kratos-sample.

How To

Create custom card

To Create a card consists of two TextView that can also handle click even2

  1. Create a class that extends KData, for example:

    public class KText extends KData {
       public KString text1;
       public KString text2;
    }
  2. 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="http://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>
  3. 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 onEventMainThread method.

  4. 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);
  5. 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:

Deme

Add Custom updater:

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!");
}

Wait

You may say:"That's no big deal. what's the point?"

But

This is where the magic happens!

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.

  1. Change the text1 element in the json file above to this:
"text1": "{Customer.name}",
  1. 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:

Customer

Dude, Holy ****, This is Magic..

Download

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"
}

How It Works

Lisence

GNU GENERAL PUBLIC LICENSE Version 3