android数据绑定框架介绍

简介: 背景 数据绑定框架有很多,其实我就看过谷歌官方的数据绑定框架,官方的框架用起来的时候,觉得不是很顺手,侵入性还比较强。而且也一直纠结彷徨,从心底里质疑数据绑定框架的价值,到底给我们开发带来了什么,实用吗,可维护吗? 某一天的早晨突然灵光一现,决定自己去试试开发一个自己喜欢的数据绑定框架,经过

github地址

地址

背景

数据绑定框架有很多,其实我就看过谷歌官方的数据绑定框架,官方的框架用起来的时候,觉得不是很顺手,侵入性还比较强。而且也一直纠结彷徨,从心底里质疑数据绑定框架的价值,到底给我们开发带来了什么,实用吗,可维护吗?

某一天的早晨突然灵光一现,决定自己去试试开发一个自己喜欢的数据绑定框架,经过没日没夜的艰苦风斗(当然是开玩笑的),中间反复修改设计,最后尘埃落定。虽然这个框架已经出世,也在生产环境试运行了一些页面,但是我对于数据绑定框架的价值,始终抱有质疑的态度,所以今天我将其取名sword,希望他像一把双刃剑,能够有一个面给开发者带来一些小小的便利。

价值

数据绑定的价值,我的理解就是一句话:就是通过操作一个数据对象,达到修改视图的目的,或者反过来,通过操作视图对象,达到修改数据对象的目的。

Screen_Shot_2016_01_11_at_9_37_37_PM

提示:在大多数的情况下,通常都是数据单向操作视图的,极少的情况下需要数据和视图双向绑定,互相操作。

Screen_Shot_2016_01_11_at_9_38_54_PM

模型分析

textView.setText(CharSequence data);
data = textView.getText();

第一行代码代表值到视图的映射,第二行代码代表视图到值的映射。

值到视图的绑定

Screen_Shot_2016_01_12_at_9_20_46_AM

视图到值的绑定

Screen_Shot_2016_01_12_at_10_46_50_AM

sword的使用方法

sword的用法分为单向绑定(常用的),双向绑定2种。

单向绑定

新建data

class Data implements BindImpl {
    @Bind(viewId = R.id.end_title,setMethod = B.setText)
    public String end_title = "1234";
    public String end_desc = "aaa";

    @Bind(viewName = "end_title", setMethod = B.setTextColor)
    public int endTitleColor=getResources().getColor(R.color.colorAccent);
    @Bind(viewName = "end_title", setMethod = B.setOnClickListener, getMethod = B.getMethodNull)
    public View.OnClickListener end_titleL = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                end_title = "88888";
                end_desc = "+++++";
                endTitleColor = getResources().getColor(R.color.colorPrimary);
                url = "~~~~~";
                bindH.dataChange();
            }
        };

}

值的写法有3种

@Bind(viewId = R.id.end_title,setMethod = B.setText)
public String end_title = "1234";
@Bind(viewName = "end_title",setMethod = B.setText)
public String end_title = "1234";
@Bind(setMethod = B.setText)
public String end_title = "1234";

如果要映射的视图是一个textview,setMethod的默认值就是“setText”那么还有第四种写法.

public String end_title = "1234";

我来解释一下,上面3种用法,直接上图。

Screen_Shot_2016_01_12_at_11_10_19_AM

天然的2个缺陷

值对应的缺陷

textView.setText(CharSequence data);

所谓绑定,就是一一对应,view的方法只能接受一个参数。抽离出关键字就是:view,一个方法参数。但是有时候没办法处理特殊情况,比如我们使用volly框架的NetworkImageView,用法是这样的

 NetworkImageView.setImageUrl(String url, ImageLoader imageLoader)

解决缺陷办法

class Data implements BindImpl {
...
        @Bind(viewName = B.viewNameSelf, setMethod = "setImageUrl")
        private String url;
        
        void setImageUrl(String url) {
            networkImageView.setImageUrl(url, imageLoader);
        }
        ....
        }

指定到data自身viewName = B.viewNameSelf,指定到自身的方法setImageUrl(名字可以任意取),在方法内部构造特殊情况。

伪值绑定的缺陷

textView.setText(int textId);

上面代码是找不到视图到值的对应,即不存在:

textId = textView.getText()

这样的对应,我将其称之为伪值对应,即只存在数据到视图的对应。
所以这样的类型我们要告知没有返回值(单向绑定的情况下不需要告知,只有双向绑定的时候才需要这么做,即添加getMethod = B.getMethodNull),代码如下:

@(Bind viewName="text",getMethod = B.getMethodNull)
int textId

完整的用法:

使用的时候只需要2句代码即可:

 data = new Data();
 swordBind.bindOneWay(this, data);

data变化时通知view变化:

    end_title = "88888";
    end_desc = "+++++";
    endTitleColor = getResources().getColor(R.color.colorPrimary);
    url = "~~~~~";
    swordBind.dataChange();

完整代码:

package com.taobao.pandora.hello;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import com.taobao.sword.BindImpl;
import com.taobao.sword.SwordBind;
import com.taobao.sword.meta.B;
import com.taobao.sword.meta.Bind;

/**
 * Created by shanksYao on 1/5/16.
 */
public class BindActivity extends Activity {
    Data data;
    SwordBind swordBind = new SwordBind();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.live_include_deal_status);
        data = new Data();
        swordBind.bindOneWay(this, data);

    }

    /**
     * data就是用来操作View   任何跟View无关的属性 请不要放进来,以免造成设计混乱
     */
    class Data implements BindImpl {

        @Bind(viewName = "end_title")
        public String end_title = "1234";


        public String end_desc = "aaa";

        @Bind(viewName = "end_title", setMethod = B.setTextColor)
        public int endTitleColor = getResources().getColor(R.color.colorAccent);

        @Bind(viewName = "end_title", setMethod = B.setOnClickListener, getMethod = B.getMethodNull)
        public View.OnClickListener end_titleL = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                end_title = "88888";
                end_desc = "+++++";
                endTitleColor = getResources().getColor(R.color.colorPrimary);
                url = "~~~~~";
                swordBind.dataChange();
            }
        };

        @Bind(viewName = "end_desc", setMethod = B.setOnClickListener)
        public View.OnClickListener end_descL = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("===", end_desc);
                end_title = "+++++";
                end_desc = "88888";
                endTitleColor = getResources().getColor(R.color.colorAccent);
                url = "++++++++";
                swordBind.dataChange();
                Log.e("===", end_desc);
            }
        };

        @Bind(viewName = B.viewNameSelf, setMethod = "setImageUrl")
        private String url;

        void setImageUrl(String url) {
            Log.e("self", "--------" + url);
        }


    }

}

双向绑定

双向绑定和单向绑定的使用类似,只不过双向绑定,需要建立一个view的java类,如下:

   class ViewHolder implements BindImpl {
        private View price_ll;
        private TextView luochui;
        private View price_num;
        private View other_win_desc;
        private View end_rl;
        private ImageView end_img;
        private TextView end_title;
        private TextView end_desc;
        private View to_my_order;
    }

使用时viewHolder的属性名就是xml里面id的名字,也就是说 private TextView luochui;对应

   <TextView
            android:id="@+id/luochui"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="落槌价:¥" />

双向绑定的用法:

        SwordBind.renderById(holder,this);
        data = new Data();
        swordBind.bind(holder,data);

view变化时,通知data变化:

        holder.luochui.setText("123");
        holder.end_desc.setText("hahahah");
        ...
        swordBind.viewChange();

优化特性:

1.getMethod不指明的情况下,是根据setMethod来推演的,推演出getMethod或者isMethod.
2.内部有一个对应常量关系,罗列了几乎所有用到的view的setMethod方法和对应的getMethod方法。
3.数据更新的时候,是变化的数据才会应用更新,内部采用差量更新的策略。

法则

  1. 尽量使用真值绑定(真值指的是具有双向绑定的值,上面提到的伪值绑定有缺陷)
  2. 单向绑定受外力可能会有影响(外力指的是除了data作用外的其他调用方式)
  3. 单向绑定很容易转化为双向绑定
  4. 尽量不要使用外力影响view的展现

请务必记住这张图

Screen_Shot_2016_01_12_at_4_48_13_PM

最新功能

  1. 2016.14 ,对 minifyenable =true 支持。## 标题 ##

Sword 框架新增实验特性

标签(空格分隔): Android


背景

这段时间一直在想能不能去掉swordBind.dataChange();或者swordBind.viewChange();代码的调用.即用户只需要关心操作数据,或者操作视图,不需要关心操作完数据后,通知sword框架数据有变化。这段时间有一些想法,虽然不是很完美,但是确实做到了去掉swordBind.dataChange();swordBind.viewChange();代码的目的。

直接代码对比

原来的代码是这样写的

        end_title = "+++++";
        end_desc = "88888";
        url = "++++++++";
        swordBind.dataChange();

现在成了这样:

        end_title = "+++++";
        end_desc = "88888";
        url = "++++++++";

少了一行swordBind.dataChange();,只不过我把这行代码隐藏起来放到了另外的地方。

如何做到

Android的UI是运行在一个loop线程上的,每一次loop运行完,android.os.Looper.loop()都会调用代码

 if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

利用logging的回调,我们可以注入一个android.util.Printer对象,每次UI的线程执行完,我们都会得到一次回调。在这个回调里面做我们想做的事情,即swordBind.dataChange();或者swordBind.viewChange();.

限定模型

我不敢保证我上述的做法在任何情况下都是OK的,但是每一次引起数据变化,肯定是存在交互,交互最后都会回溯到UI线程上面,在UI线程的底部完成交互。即使是网络调用,AsycTask依赖会回调到UI线程上onPostExecute.
根据这一普适的特性,我可以构造一个属于我的模型,在这个模型里面我可以保证这一个实验特性的正确性。
Screen_Shot_2016_01_18_at_3_11_36_PM

收窄模型,即只要遵循data的值操作都是在UI线程上进行,(视图操作就不用说了,肯定在UI线程操作)

Screen_Shot_2016_01_19_at_9_27_03_AM

总结: 只要data的值操作都是在UI线程上进行,那么这个模型一定有效。这个实验特性也是一样有效

核心代码

  /////check value change
    private boolean useExperiment=false;
    private static List<SwordBind> swordBindList = new ArrayList<>();
    private static boolean isInitSmart=false;

    public void setUseExperiment(boolean useExperiment) {
        this.useExperiment = useExperiment;
    }

    private void initSmartCheck(final Context context) {
        if (!isInitSmart) {
            context.getMainLooper().setMessageLogging(new Printer() {
                @Override
                public void println(String x) {
                    if(x.startsWith("<<<<< Finished to"))
                    for (SwordBind swordBind : swordBindList) {
                        swordBind.dataChange();
                        swordBind.viewChange();
                    }
                }
            });
            isInitSmart = true;
        }
    }

    public void onPause(){
        if(useExperiment)
        swordBindList.remove(this);
    }
    public void onResume(){
        if(!useExperiment)
            return;
        if(parent!=null)
            initSmartCheck(parent.getContext());
        else
            initSmartCheck(activity);
        swordBindList.add(this);
    }

效率

为了平衡最少代码调用和代码执行效率2个方面,我苦苦挣扎了好几天,最后还是效率为王,即sword所在的activity或者fragment当前是否在前台,需要主动告诉我。如果在adaptor里一定要用这个实验特性,需要构造2个回调方法,让activity或者fragment回调。

用法

开启实验特性

 swordBind.setUseExperiment(true);

开启完毕之后,调用2个核心代码即可:

    @Override
    protected void onResume() {
        super.onResume();
        swordBind.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        swordBind.onPause();
    }

完整代码:

package com.taobao.pandora.shanks;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.taobao.pandora.sword.BindImpl;
import com.taobao.pandora.sword.SwordBind;
import com.taobao.pandora.sword.meta.B;
import com.taobao.pandora.sword.meta.Bind;
/**
 * Created by shanksYao on 1/5/16.
 */
public class BindActivity extends Activity {
    Data data;
    SwordBind swordBind = new SwordBind();

     ViewHolder holder = new ViewHolder();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.live_include_deal_status);
        swordBind.renderById(holder,this);
        data = new Data();
        swordBind.bind(holder,data);
        swordBind.setUseExperiment(true);
    }

    @Override
    protected void onResume() {
        super.onResume();
        swordBind.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        swordBind.onPause();
    }

    class ViewHolder implements BindImpl {
        private View price_ll;
        private TextView luochui;
        private View price_num;
        private View other_win_desc;
        private View end_rl;
        private ImageView end_img;
        private TextView end_title;
        private TextView end_desc;
        private View to_my_order;
    }


    /**
     * data就是用来操作View   任何跟View无关的属性 请不要放进来,以免造成设计混乱
     */
    class Data implements BindImpl {

        @Bind(viewName = "end_title",setMethod = B.setText,getMethod = "getText")
        public CharSequence end_title = "1234";


        public String end_desc = "aaa";

        @Bind(viewName = "end_title", setMethod = B.setTextColor)
        public int endTitleColor = getResources().getColor(R.color.colorAccent);

        @Bind(viewName = "end_title", setMethod = B.setOnClickListener, getMethod = B.getMethodNull)
        public View.OnClickListener end_titleL = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               /* end_title = "88888";
                end_desc = "+++++";
                endTitleColor = getResources().getColor(R.color.colorPrimary);
                url = "~~~~~";*/
                holder.end_title.setText("123");
                holder.end_desc.setText("hahahah");
            //    swordBind.dataChange();
            }
        };

        @Bind(viewName = "end_desc", setMethod = B.setOnClickListener)
        public View.OnClickListener end_descL = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("===", end_desc);
                end_title = "+++++";
                end_desc = "88888";
                endTitleColor = getResources().getColor(R.color.colorAccent);
                url = "++++++++";
             //   swordBind.dataChange();
                Log.e("===", end_desc);
            }
        };

        //
        void setEndTitle(int id) {
            end_title = getResources().getString(id);
            //swordBind.dataChange();
        }

        void setUrl(ViewHolder holder, String url) {
            /////设置方法处理
            holder.end_img.setImageDrawable(null);
        }

        @Bind(viewName = B.viewNameSelf, setMethod = "setImageUrl")
        private String url;

        void setImageUrl(String url) {
            Log.e("self", "--------" + url);
        }


    }
    ////Ui数据绑定


}
相关文章
|
3月前
|
开发工具 Android开发 开发者
Android平台如何不推RTMP|不发布RTSP流|不实时录像|不回传GB28181数据时实时快照?
本文介绍了一种在Android平台上实现实时截图快照的方法,尤其适用于无需依赖系统接口的情况,如在RTMP推送、RTSP服务或GB28181设备接入等场景下进行截图。通过底层模块(libSmartPublisher.so)实现了截图功能,封装了`SnapShotImpl.java`类来管理截图流程。此外,提供了关键代码片段展示初始化SDK实例、执行截图、以及在Activity销毁时释放资源的过程。此方案还考虑到了快照数据的灵活处理需求,符合GB/T28181-2022的技术规范。对于寻求更灵活快照机制的开发者来说,这是一个值得参考的设计思路。
|
3月前
|
物联网 区块链 vr&ar
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
|
1天前
|
算法 JavaScript Android开发
|
10天前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
37 1
|
23天前
|
存储 大数据 数据库
Android经典面试题之Intent传递数据大小为什么限制是1M?
在 Android 中,使用 Intent 传递数据时存在约 1MB 的大小限制,这是由于 Binder 机制的事务缓冲区限制、Intent 的设计初衷以及内存消耗和性能问题所致。推荐使用文件存储、SharedPreferences、数据库存储或 ContentProvider 等方式传递大数据。
36 0
|
2月前
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
288 3
|
2月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
69 8
|
3月前
|
设计模式 Java Android开发
探索安卓应用开发:从新手到专家的旅程探索iOS开发中的SwiftUI框架
【8月更文挑战第29天】本文旨在通过一个易于理解的旅程比喻,带领读者深入探讨安卓应用开发的各个方面。我们将从基础概念入手,逐步过渡到高级技术,最后讨论如何维护和推广你的应用。无论你是编程新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和实用的代码示例。让我们一起开始这段激动人心的旅程吧!
|
3月前
|
Android开发
基于Amlogic 安卓9.0, 驱动简说(三):使用misc框架,让驱动更简单
如何使用Amlogic T972安卓9.0系统上的misc框架来简化驱动程序开发,通过misc框架自动分配设备号并创建设备文件,从而减少代码量并避免设备号冲突。
37 0
基于Amlogic 安卓9.0, 驱动简说(三):使用misc框架,让驱动更简单
|
3月前
|
JSON Java Android开发
Android 开发者必备秘籍:轻松攻克 JSON 格式数据解析难题,让你的应用更出色!
【8月更文挑战第18天】在Android开发中,解析JSON数据至关重要。JSON以其简洁和易读成为首选的数据交换格式。开发者可通过多种途径解析JSON,如使用内置的`JSONObject`和`JSONArray`类直接操作数据,或借助Google提供的Gson库将JSON自动映射为Java对象。无论哪种方法,正确解析JSON都是实现高效应用的关键,能帮助开发者处理网络请求返回的数据,并将其展示给用户,从而提升应用的功能性和用户体验。
74 1