7

I am working with two recyclerview in single screen(For android TV).Each recyclerview have complex layout item.And it's taking time to load.I worked with asynclayoutinflator in activities.

    AsyncLayoutInflater inflater = new AsyncLayoutInflater(this);
    inflater.inflate(R.layout.main, null, callback);

I want to know whether there is any ways to achieve the same with recyclerview. Problem I am facing is onbindviewholder is getting called before asyncinflation finished.

Mani
  • 2,599
  • 4
  • 30
  • 49

3 Answers3

3
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import androidx.asynclayoutinflater.view.AsyncLayoutInflater

class AsyncFrameLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
    defStyleRes: Int = 0
) : FrameLayout(
    context,
    attrs,
    defStyleAttr,
    defStyleRes
) {
    init {
        layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)
    }

    private var isInflated = false
    private var pendingActions: MutableList<AsyncFrameLayout.() -> Unit> = ArrayList()

    fun inflateAsync(layoutResId: Int) {
        AsyncLayoutInflater(context).inflate(layoutResId, this) { view, _, _ ->
            addView(view)
            isInflated = true
            pendingActions.forEach { action -> action() }
            pendingActions.clear()
        }
    }

    fun invokeWhenInflated(action: AsyncFrameLayout.() -> Unit) {
        if (isInflated) {
            action()
        } else {
            pendingActions.add(action)
        }
    }
}

How to use:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    val itemView = AsyncFrameLayout(parent.context)
    itemView.inflateAsync(R.layout.my_layout)
    return MyViewHolder(itemView)
}

override fun onBindViewHolder(viewHolder: MyViewHolder, position: Int) {
    viewHolder.itemView.invokeWhenInflated {

    }
}
Artem Odnovolov
  • 101
  • 1
  • 4
1

Don't know if it's exactly what you're looking for but I am thinking about setting the items in your recycler's adapter after the inflater done his job. This way, before the inflate(...) method your adapter getCount() will return 0 and onBindViewHolder will not be called anymore.

  • 1
    Yes. This is the problem I am facing. Is there any way I can do background inflation of my recyclerview item layout? – yadunath.narayanan Dec 05 '18 at 14:29
  • 1
    @yadunath.narayanan what you can do is onCreateViewHolder return a view holder with an empty FrameLayout while at the same time kick off async inflation with `frameLayout.addView(asyncInflatedView)` in the callback. I can imagine some potential racing issues if the view has been recycled before inflation is complete (just to mention) – Arturs Vancans Aug 09 '19 at 07:10
0

There is actually little to none resource for AsyncLayoutInflator in RecyclerView using Java, i actually had to write my own version of @Artem's answer in Java using few Kotlin resources available over the internet.

First we use a simple dummy view for recyclerview so that the recycleview loads quickly, and add the rest of the complicated view to the simpleview afterwards in background using asyncLayoutInflator.

The reason onBindExecutes executes quickly and you are unable to access the complicated layout is that the onCreate method creates only the dummy view, so when you try to access the complicated view it retunes a null exception as complicated views are yet to be generated. To overcome this, we pass the list to the class where the complicated views are being generated and update the view accordingly.

Below is the program on how its being implemented. My code maybe a little messy as im still learning, and can be improved further upon.

MyListData.Java

public class MyListData{  
    private String description;  
    private int imgId;  
    public MyListData(String description, int imgId) {  
        this.description = description;  
        this.imgId = imgId;  
    }  
    public String getDescription() {  
        return description;  
    }  
    public void setDescription(String description) {  
        this.description = description;  
    }  
    public int getImgId() {  
        return imgId;  
    }  
    public void setImgId(int imgId) {  
        this.imgId = imgId;  
    }  
}  

MainActivity.java


public class MainActivity extends AppCompatActivity {  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        MyListData[] myListData = new MyListData[] {  
                new MyListData("Email", android.R.drawable.ic_dialog_email),  
                new MyListData("Info", android.R.drawable.ic_dialog_info),  
                new MyListData("Delete", android.R.drawable.ic_delete),  
                new MyListData("Dialer", android.R.drawable.ic_dialog_dialer),  
                new MyListData("Alert", android.R.drawable.ic_dialog_alert),  
                new MyListData("Map", android.R.drawable.ic_dialog_map),  
                new MyListData("Email", android.R.drawable.ic_dialog_email),  
                new MyListData("Info", android.R.drawable.ic_dialog_info),  
                new MyListData("Delete", android.R.drawable.ic_delete),  
                new MyListData("Dialer", android.R.drawable.ic_dialog_dialer),  
                new MyListData("Alert", android.R.drawable.ic_dialog_alert),  
                new MyListData("Map", android.R.drawable.ic_dialog_map),  
        };  
  
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);  
        MyListAdapter adapter = new MyListAdapter(myListData);  
        recyclerView.setHasFixedSize(true);  
        recyclerView.setLayoutManager(new LinearLayoutManager(this));  
        recyclerView.setAdapter(adapter);  
    }  
}  

MyListAdapter


public class MyListAdapter extends ListAdapter<RecyclerView.ViewHolder> {
  private MyListData[] listdata;  
  
   // RecyclerView recyclerView;  
    public MyListAdapter(MyListData[] listdata) {  
        this.listdata = listdata;  
    }   


    @NonNull
    @Override

    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {


        DummyView dummyView= new DummyView(parent.getContext());
        dummyView.inflate();

        return new viewHolder(dummyView);

    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

        setupDummyView(holder, position);
     }


    }

    private void setupDummyView(RecyclerView.ViewHolder holder, int position) {
        DummyView items = (DummyView) holder.itemView;

            items.bindWhenInflated(items.textView, listdata[position]);  - - - -> bindWhenInflated is a method in DummyViewClass which extends AsyncLayoutClass

    }

    @Override
    public int getItemCount() {
        return  return listdata.length;
    }


    static class ListViewHolder extends RecyclerView.ViewHolder {

        public ListViewHolder(ViewGroup view) {
            super(view);
        }

    }


    private static final class DummyView extends AsyncLayout {

        public Context context;

        MaterialTextView textView;
        private final int layoutId = R.layout.datacolumn;


        public DummyView(@NonNull Context context) {
            super(context);
            this.context = context;
        }


        public int getLayoutId() {
            return this.layoutId;
        }

        @Override
        public View getView(View view) {
            textView = view.findViewById(R.id.dataText);
         
            return super.getView(view);
        }

        public MaterialTextView getTextView() {
            return textView;
        }
    }

}

AsyncLayout


public class AsyncLayout extends FrameLayout {

    private final int layoutId;
    private boolean isInflated = false;
    private Context context;
    private final List<MyListData>bindingfunction;


    public AsyncLayout(Context context) {
        super(context);
        this.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        this.context = context;
        layoutId = -1;
        bindingfunction = new ArrayList<>();

    }

    public int getLayoutId() {
        return layoutId;
    }

    public final void inflate() {

        AsyncLayoutInflater asyncInflator = new AsyncLayoutInflater(context);
        asyncInflator.inflate(this.getLayoutId(),this, (view, resid, parent) -> {
            isInflated = true;
            assert parent != null;
            parent.addView(getView(view));

        });
    }



    public View getView(View view) {

        MaterialTextView textView = view.findViewById(R.id.listText);
        for (MyListData data :bindingfunction
             ) {

            textView.setText(data.getDescription());
        }
        return view;
    }


    public void bindWhenInflated(MaterialTextView textView, MyListData listData) {
        if(isInflated){
            textView.setText(listdata.getDescription());
        }else{
            bindingfunction.add(listData);

        }
    }
}

I like to conclude saying AsyncLayoutInflator should be used in limited circumstances as its improper implementation might result in unexpected UI behaviour.

Dikyashi
  • 1
  • 2