The meaning of the DataBinding

1, the layout file is usually only responsible for the layout of THE UI control, the page through the code of the control needs to carry out a variety of operations, bear the vast majority of the workload

DataBinding lets layout files do some of the work of the page and further decouple layout files from the page

3. Make UI controls directly bind to fields in the data model and even respond to user interactions. Convenient implementation of MVVM

DataBinding is easy to use

1. Start DataBinding

In the build.gradle file under the module, start dataBinding.

android {
    dataBinding {
        enabled = true}} Higher versions use Android {buildFeatures {dataBinding =true}}Copy the code

2. Convert the normal layout file to the DataBinding layout file

You can use the IDE’s features for automatic conversion in the layout file root TAB right click

The conversion is as follows:


      
<! The DataBinding layout root tag is the Layout tag -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <! Write classes or variables that need to be referenced in the layout file.
    </data>

    <! -- Original layout file content -->
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="HelloWorld"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Copy the code

3. Instantiate the layout file

The Activity of the setContentView (R.l ayout. XXX) to DataBindingUtil. The setContentView ()

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        // Instantiate the DataBinding object
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
    }
Copy the code

4. Pass the data to the layout file

After step 3, the layout file is converted to an instance object, so you can bind data to that instance and use the data in the layout file

Use data in layout files:


      
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
    	<! - binding book object, type of com. Breeze. Jetpackstudy. Model. The book -- -- >
        <variable
            name="book"
            type="com.breeze.jetpackstudy.model.Book" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
		
        <! -- Android :text="@{book.name}" --android:text="@{book.name}" --android:text="@{book.name}" -- Android :text="@{book.name}" -- Android :text="@{book.name}"
        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{book.name}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <! -- Android :text="@{book.author}" --android:text="@{book.author}"
        <TextView
            android:id="@+id/author"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/name"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:text="@{book.author}"
            android:layout_marginTop="20dp" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Copy the code

For the layout file instance, bind the book data object, and DataBinding generates the set method for ease of use, as in this case binding.book, which can be called directly

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        binding.book = Book("DataBinding Study"."Breeze")}Copy the code

5. Running results

Reference static classes in the layout file

There are many scenarios where we need to do some processing for the presentation of data, for example, the data bean field returned by the network is a number, and the presentation should be transformed into the corresponding document. The old way to do it is to do it in code and then reset it to the view. Using Databinding you can use static class transformation directly in the layout file.

Static utility classes:

class BookRatingUtil {

    companion object {
        @JvmStatic
        fun getRatingString(rate : Int) =
            when (rate) {
                0 -> "Odd"
                1 -> "A week"
                2 -> "The star"
                3 -> "Samsung"
                4 -> "Four-star"
                5 -> "Five star"
                else -> ""}}}Copy the code

Layout file:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="book"
            type="com.breeze.jetpackstudy.model.Book" />
        <! --import reference utility class -->
        <import type="com.breeze.jetpackstudy.BookRatingUtil"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{book.name}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/author"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/name"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:text="@{book.author}"
            android:layout_marginTop="20dp" />

        <! Use utility class to convert book.rate -->
        <TextView
            android:id="@+id/rate"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/author"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:text="@{BookRatingUtil.getRatingString(book.rate)}"
            android:layout_marginTop="20dp"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Copy the code

Third, secondary page binding

When a layout file is complex, include tags are used to split the layout. How do you transfer data using DataBinding?

The level-1 page is as follows:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="book"
            type="com.breeze.jetpackstudy.model.Book" />
        <import type="com.breeze.jetpackstudy.BookRatingUtil"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
		
        <! -- Pass data to secondary page -->
        <include layout="@layout/include_layout"
            app:book="@{book}"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Copy the code

Secondary pages receive the passed data:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="book"
            type="com.breeze.jetpackstudy.model.Book" />
        <import type="com.breeze.jetpackstudy.BookRatingUtil"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".SecondLevelActivity">

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{book.name}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/author"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/name"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:text="@{book.author}"
            android:layout_marginTop="20dp" />

        <TextView
            android:id="@+id/rate"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/author"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:text="@{BookRatingUtil.getRatingString(book.rate)}"
            android:layout_marginTop="20dp"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Copy the code

DataBinding responds to events

1. Layout content is as follows:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="eventHandler"
            type="com.breeze.jetpackstudy.EventHandler" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
		
        <! EventHandler ::onBtnClick = eventHandler::onBtnClick = eventHandler::onBtnClick
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Click Me"
            android:onClick="@{eventHandler.onBtnClick}"/>

    </LinearLayout>
</layout>
Copy the code

2, the EventHandler class

class EventHandler(private val context : Context) {
	// Use the same method parameter as OnClickListener
    fun onBtnClick(view: View) {
        Toast.makeText(context, "I am Clicked", Toast.LENGTH_SHORT).show()
    }

}
Copy the code

3, the binding

class EventActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityEventBinding>(this, R.layout.activity_event)
        binding.eventHandler = EventHandler(this)}}Copy the code

Customize the BindingAdapter

Take adding custom attributes to ImageView as an example

1, write ImageViewBindingAdapter class, note that the method needs to be written as a static method

class ImageViewBindingAdapter {

    companion object {
        @JvmStatic
        @BindingAdapter("image")
        fun setImage(imageView : ImageView, imageUrl : String) {
            if(! TextUtils.isEmpty(imageUrl)) { Picasso.with(imageView.context) .load(imageUrl) .into(imageView) } }// The image attribute can be used with multiple parameters. The image attribute can be used with multiple parameters
        @JvmStatic
        @BindingAdapter(value = ["image"."defaultImageResource"], requireAll = false)
        fun setImage(imageView: ImageView, imageUrl: String, imageResource : Int) {
            Log.e("Breeze"."setImage, image defaultImageResource")
            if(! TextUtils.isEmpty(imageUrl)) { Picasso.with(imageView.context) .load(imageUrl) .into(imageView) }else {
                imageView.setImageResource(imageResource)
            }
        }
    }

}
Copy the code

2. Use in layout

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="networkImage"
            type="String" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
		<! App :image property -->
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:image="@{networkImage}"/>

    </LinearLayout>
</layout>
Copy the code

3. Bind networkImage

val binding = DataBindingUtil.setContentView<ActivityBindingadapterBinding>(this, R.layout.activity_bindingadapter)
        binding.networkImage = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606037072884&di=425fa1754c78e7288acf2bf8797750ba&i mgtype=0&src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F00%2F89%2F93%2F6156ee970479161.jpg"
Copy the code

4. The BindingAdapter defined by DataBinding itself

ViewBindingAdapter source code, defines the view attribute related bindings

ImageViewBindingAdapter source code:

When are these methods called in our subsequent analysis ~

6. BaseObservable implements bidirectional binding

In the above cases, one-way binding is implemented (the interface changes when the data changes, but the data does not change when the interface changes). The effect of bidirectional binding is that in addition to data affecting the interface, interface changes also cause data to change. For example, the bound data bean changes as the EditText inputs content. You can do this using a BaseObservable

class LoginModel(var userName : String)

// BaseObservable must be inherited
class TwoWayBindingViewModel : BaseObservable() {

    private val loginModel : LoginModel = LoginModel("Breeze")

    // The get method must have a Bindable annotation
    @Bindable
    fun getUserName(a) : String {
        return loginModel.userName
    }

    fun setUserName(userName : String) {
        // Critical point, where you must determine whether the data has changed, otherwise an infinite loop will occur. When the interface changes, it's called back here
        if(userName ! = loginModel.userName) { loginModel.userName = userName Log.e("ZXX"."${loginModel.userName}")
            // Fixed, where notifyPropertyChanged is called
            notifyPropertyChanged(BR.userName)
        }
    }
}
Copy the code

Use in layout

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="loginModel"
            type="com.breeze.jetpackstudy.model.TwoWayBindingViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <! -- use @= for bidirectional binding -->
        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={loginModel.userName}"/>

    </LinearLayout>
</layout>
Copy the code

Activity injects data

class TwoWayActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityTwowayBinding>(this, R.layout.activity_twoway)
        binding.loginModel = TwoWayBindingViewModel()
    }

}
Copy the code

Use ObservableField- more elegant bidirectional binding

When BaseObservable implements bidirectional binding, it writes “fixed notation” in many places, which is a less elegant implementation because it is prone to error.

Change the TwoWayBindingViewModel implementation to use ObservableField to wrap the data Bean without changing the code binding the data in the layout file and Activity

class TwoWayBindingViewModel {

    private val loginObservableField : ObservableField<LoginModel> = ObservableField()

    init {
        loginObservableField.set(LoginModel("Breeze"))}fun getUserName(a):String {
        return loginObservableField.get()? .userName? :""
    }

    fun setUserName(userName:String) {
        loginObservableField.get()? .userName = userName Log.e("ZXX"."${loginObservableField.get()? .userName}")}}Copy the code

Eight, the actual use – more elegant packaging RecyclerView

1, the idea of using BindingAdapter for RecyclerView custom properties

class RecyclerViewBindingAdapter {

    companion object {

        @JvmStatic
        @BindingAdapter("adpter")
        fun setAdapter(view : RecyclerView, adapter : RecyclerView.Adapter< * >) {
            view.adapter = adapter
        }

        @JvmStatic
        @BindingAdapter("list")
        fun setList(view : RecyclerView, vm : RecyclerViewModel) {
            (view.adapter as BookAdapter).setList(vm.bookLiveData.value)
        }

    }

}
Copy the code

2, use RecyclerView layout

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="adapter"
            type="com.breeze.jetpackstudy.adapter.BookAdapter" />
        <variable
            name="vm"
            type="com.breeze.jetpackstudy.RecyclerViewModel" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:adapter="@{adapter}"
            app:list="@{vm}"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            />

    </RelativeLayout>

</layout>
Copy the code

As you can see, adapter, LayoutManager, and data List are all set using dataBinding, so you don’t need to set the properties of RecyclerView that you used to have to add

3. Activity code

class RecyclerViewActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityRecyclerviewBinding>(this, R.layout.activity_recyclerview)
        val model = ViewModelProviders.of(this).get(RecyclerViewModel::class.java)
        binding.apply {
            // This lifecycleOwner must be set if it uses ViewModel
            lifecycleOwner = this@RecyclerViewActivity
            vm = model
            adapter = BookAdapter()
        }
    }

}
Copy the code

4. The layout file for each item in the list also uses DataBinding to bind data

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="book"
            type="com.breeze.jetpackstudy.model.Book" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="50dp">
		<! -- Bind data -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{book.name}" />

    </RelativeLayout>
</layout>
Copy the code

5. Compiled Adapter

class BookAdapter : RecyclerView.Adapter<BookAdapter.BookViewHolder>() {

    private var list : List<Book>? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = DataBindingUtil.inflate<BookItemBinding>(inflater, R.layout.book_item, parent,false)
        return BookViewHolder(binding)
    }

    override fun getItemCount(a): Int {
        returnlist? .size? :0
    }

    override fun onBindViewHolder(holder: BookViewHolder, position: Int) {
        valbook = list!! [position]// Refresh data using databinding instead of writing various findViewById Settings ~
        holder.binding.book = book
    }

    fun setList(list : List<Book>? {
        if(list ! =null) {
            this.list = list
            notifyDataSetChanged()
        }
    }

    class BookViewHolder constructor(val binding : BookItemBinding) : RecyclerView.ViewHolder(binding.root)

}
Copy the code

9. Implementation principle of DataBinding

Using the simple example from MainActivity, only one-way binding is analyzed

Take a look at the simplest binding procedure to see how layout data is bound. Use jADX decompiler to view demo code, MainActivity code:

public final class MainActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); Here to generate the binding ActivityMainBinding binding = (ActivityMainBinding) DataBindingUtil. The setContentView (this, R.layout.activity_main);
        Book book = new Book("DataBinding Study"."Breeze".5);
        Intrinsics.checkExpressionValueIsNotNull(binding, "binding");
        // Refresh the interface after setting the data
        binding.setBook(book);
        new Handler().postDelayed(new MainActivity$onCreate$1(binding, book), 1000); }}Copy the code

The ActivityMainBinding generation process:

DataBindingUtil. The setContentView source code:

    public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId, DataBindingComponent bindingComponent) {
        // setContentView is already called
        activity.setContentView(layoutId);
        // Generate the DataBinding object,
        return bindToAddedViews(bindingComponent, (ViewGroup) activity.getWindow().getDecorView().findViewById(16908290), 0, layoutId);
    }
Copy the code

The bindToAddedView code will go to the bind method

  static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root, int layoutId) {
        return sMapper.getDataBinder(bindingComponent, root, layoutId);
    }
Copy the code
    public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
        LayoutId = 1,2,3... layoutId = 1, 3...
        int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
        if (localizedLayoutId <= 0) {
            return null;
        }
        //DataDinding processes the layout and adds a tag to it
        Object tag = view.getTag();
        if(tag ! =null) {
            switch (localizedLayoutId) {
                    // There are a lot of cases omitted here.case 3:
                    if ("layout/activity_main_0".equals(tag)) {
                        return new ActivityMainBindingImpl(component, view);
                    }
                    throw new IllegalArgumentException("The tag for activity_main is invalid. Received: "+ tag); .default:
                    return null; }}else {
            throw new RuntimeException("view must have a tag"); }}Copy the code

The key point is the logic implemented by the code in ActivityMainBindingImpl

// The generated class inherits from the ActivityMainBinding class
class ActivityMainBindingImpl extends ActivityMainBinding
//ActivityMainBindingAnd inheritanceViewDataBindingOf the classabstract class ActivityMainBinding extends ViewDataBinding

private ActivityMainBindingImpl(DataBindingComponent bindingComponent.View root.Object[] bindings) {
        super(bindingComponent, root, 0, bindings[2], bindings[1], bindings[3]);
        this.mDirtyFlags = -1;
        this.author.setTag((Object) null);
        ConstraintLayout constraintLayout = bindings[0];
        this.mboundView0 = constraintLayout;
        constraintLayout.setTag((Object) null);
        this.name.setTag((Object) null);
        this.rate.setTag((Object) null);
        setRootTag(root);
        // The interface data is really being processed here
        invalidateAll();
    }

	// The actual method of binding the data will be analyzed below.
   public void executeBindings(a) {
        long dirtyFlags;
        synchronized (this) {
            dirtyFlags = this.mDirtyFlags;
            this.mDirtyFlags = 0;
        }
        String bookName = null;
        int bookRate = 0;
        Book book = this.mBook;
        String bookRatingUtilGetRatingStringBookRate = null;
        String bookAuthor = null;
        if ((dirtyFlags & 3) != 0) {
            if(book ! =null) {
                bookName = book.getName();
                bookRate = book.getRate();
                bookAuthor = book.getAuthor();
            }
            bookRatingUtilGetRatingStringBookRate = BookRatingUtil.getRatingString(bookRate);
        }
        if ((3& dirtyFlags) ! =0) {
        	// Set the view data using various BindingAdapters
            TextViewBindingAdapter.setText(this.author, bookAuthor);
            TextViewBindingAdapter.setText(this.name, bookName);
            TextViewBindingAdapter.setText(this.rate, bookRatingUtilGetRatingStringBookRate); }}Copy the code
  public void invalidateAll(a) {
        synchronized (this) {
            this.mDirtyFlags = 2;
        }
        // Request the binding, implemented in the parent ViewDataBinding class
        requestRebind();
    }
Copy the code

The logic inside the requestRebind method in the ViewDataBinding class

// Only the important logic is listed here
private static final boolean USE_CHOREOGRAPHER = SDK_INT >= 16;
// Determining that version is greater than 16, Choreographer will perform callbacks when vsync signals are received, and mFrameCallBack is really just wrapping RebindRunnable
if (USE_CHOREOGRAPHER) {
                mChoreographer.postFrameCallback(mFrameCallback);
            } else {
                mUIThreadHandler.post(mRebindRunnable);
            }

// Code in RebindRunnable
   public void executePendingBindings(a) {
        if (mContainingBinding == null) {
            executeBindingsInternal();
        } else{ mContainingBinding.executePendingBindings(); }}// Finally executeBindingsInternal executes the executeBindings method in the ActivityMainBindingImpl class and binds to the data. ActivityMainDataBinding at initialization time.

Copy the code

How to refresh data in setBook:

    public void setBook(Book Book) {
        this.mBook = Book;
        synchronized (this) {
            this.mDirtyFlags |= 1;
        }
        notifyPropertyChanged(2);
        // You can see that this is actually going to the requestRebind method
        super.requestRebind();
    }
Copy the code

In summary, the single-phase binding actually ends up calling various BindingAdapter classes to bind data and refresh the interface. The window refreshes when the vsync signal comes after the data is set.