Tuesday, 3 February 2015

RecyclerView in Android 5.0(Lollipop)

13:00:00 Posted by Kumanan ,

The RecyclerView is a new ViewGroup that is prepared to render any adapter-based view in a similar way. It is supossed to be the successor of ListView and GridView, and it can be found in the latest support-v7 version.

RecyclerView is the appropriate view to use when you have multiple items of the same type and it’s very likely that your user’s device cannot present all of those items at once. Possible examples are contacts, customers, audio files and so on. The user has to scroll up and down to see more items and that’s when the recycling and reuse comes into play. As soon as a user scrolls a currently visible item out of view, this item’s view can be recycled and reused whenever a new item comes into view.

If you want to use a RecyclerView, you will need to feel comfortable with three elements:
– RecyclerView.Adapter
– LayoutManager
– ItemAnimator

To download the complete source code click here.


First of all here’s the layout file containing the RecyclerView:

// activity_feefs_list.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MyActivity">

    <view
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        class="android.support.v7.widget.RecyclerView"
        android:id="@+id/recycler_view"
        android:layout_alignParentStart="true" />
</RelativeLayout>


// list_row.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="2dp"
    android:id="@+id/assadas"
    android:weightSum="1">

    <ImageView
        android:id="@+id/thumbnail"
        android:layout_width="100dp"
        android:layout_height="90dp"
        android:layout_alignParentTop="true"
        android:layout_gravity="center_horizontal"
        android:scaleType="fitCenter"
        android:layout_marginLeft="5dp"
        android:src="@drawable/placeholder" />

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="233dp"
        android:layout_height="match_parent"
        android:id="@+id/textLayout"
        android:layout_weight="0.56">

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentLeft="true"
            android:layout_margin="8dp"
            android:text="Spring roll"
            android:textSize="15sp"
            android:textStyle="bold"
            android:maxLines="1" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="Description Text"
            android:layout_marginLeft="8dp"
            android:id="@+id/description"
            android:maxLines="3"/>

    </LinearLayout>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="match_parent">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/imageView"
            android:background="@drawable/sendtokitchen"/>
    </LinearLayout>

</LinearLayout>


RecyclerView.Adapter

RecyclerView includes a new kind of adapter. It’s a similar approach to the ones you already used, but with some peculiarities, such as a required ViewHolder. You will have to override two main methods: one to inflate the view and its view holder, and another one to bind data to the view. The good thing about this is that first method is called only when we really need to create a new view. No need to check if it’s being recycled.

public class MyRecyclerAdapter extends RecyclerView.Adapter<FeedListRowHolder>{


    private List<FeedItem> feedItemList;

    private Context mContext;

    public MyRecyclerAdapter(Context context, List<FeedItem> feedItemList) {
        this.feedItemList = feedItemList;
        this.mContext = context;
    }

    @Override
    public FeedListRowHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_row, null);
        FeedListRowHolder mh = new FeedListRowHolder(v);
         return mh;
    }

    @Override
    public void onBindViewHolder(FeedListRowHolder feedListRowHolder, int i) {
        final FeedItem feedItem = feedItemList.get(i);

        Picasso.with(mContext).load(feedItem.getThumbnail())
                .error(R.drawable.placeholder)
                .placeholder(R.drawable.placeholder)
                .into(feedListRowHolder.thumbnail);

        feedListRowHolder.title.setText(Html.fromHtml(feedItem.getTitle()));
        feedListRowHolder.description.setText(feedItem.getDescription());

        feedListRowHolder.imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext,""+feedItem.getTitle(),Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public int getItemCount() {
        return (null != feedItemList ? feedItemList.size() : 0);
    }
}

ViewHolder

ViewHolders are basically caches of your View objects. The Android team has been recommending using the ViewHolder pattern for a very long time, but they never actually enforced the use of it. Now with the new Adapter you finally have to use this pattern.

public class FeedListRowHolder extends RecyclerView.ViewHolder{
    protected ImageView thumbnail;
    protected TextView title;
    protected  TextView description;
    protected  ImageView imageView;

    public FeedListRowHolder(View view) {
        super(view);
        this.thumbnail = (ImageView) view.findViewById(R.id.thumbnail);
        this.title = (TextView) view.findViewById(R.id.title);
        this.description = (TextView) view.findViewById(R.id.description);
        this.imageView = (ImageView) view.findViewById(R.id.imageView);
    }

}


RecyclerView.LayoutManager

The LayoutManager is probably the most interesting part of the RecyclerView. This class is responsible for the layout of all child views. There is one default implementation available: LinearLayoutManager which you can use for vertical as well as horizontal lists.

You have to set a LayoutManager for your RecyclerView otherwise you will see an exception at Runtime.

// if your not using LinearLayoutManager
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));


LinearlayoutManager

The implementation of LinearLayoutManager is rather complex and I only had a look at some key aspects. I will return to this implementation in my post about custom LayoutManagers.

To use the LinearLayoutManager you simply have to instantiate it, tell it which orientation to use and you are done:

LinearLayoutManager layoutManager = new LinearLayoutManager(context);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
layoutManager.scrollToPosition(currPos);
recyclerView.setLayoutManager(layoutManager);

LinearLayoutManager also offers some methods to find out about the first and last items currently on screen:

- findFirstVisibleItemPosition()
- findFirstCompletelyVisibleItemPosition()
- findLastVisibleItemPosition()
- findLastCompletelyVisibleItemPosition()

RecyclerView.ItemAnimator

The ItemAnimator class helps the RecyclerView with animating individual items. ItemAnimators deal with three events:

An item gets added to the data set
An item gets removed from the data set
An item moves as a result of one or more of the previous two operations
Luckily there exists a default implementation aptly named DefaultItemAnimator. If you do not set a custom ItemAnimator, RecyclerView uses an instance of DefaultItemAnimator.

Obviously for animations to work, Android needs to know about changes to the dataset. For this Android needs the support of your adapter. In earlier versions of Android you would call notifyDataSetChanged() whenever changes occured, this is no longer appropriate. This method triggers a complete redraw of all (visible) children at once without any animation. To see animations you have to use more specific methods.

The RecyclerView.Adapter class contains plenty of notifyXyz() methods. The two most specific are:

public final void notifyItemInserted(int position)
public final void notifyItemRemoved(int position)


The Java code is also pretty simple:

public class FeedListActivity extends Activity {

    private static final String TAG = "RecyclerViewExample";

    private List<FeedItem> feedItemList = new ArrayList<FeedItem>();

    private RecyclerView mRecyclerView;

    private MyRecyclerAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        /* Allow activity to show indeterminate progressbar */
        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);

        setContentView(R.layout.activity_feeds_list);

        /* Initialize recyclerview */
        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        /*Downloading data from below url*/
        final String url = "http://www.mocky.io/v2/54cf645364b65b3608610979";
        new AsyncHttpTask().execute(url);

    }

    public class AsyncHttpTask extends AsyncTask<String, Void, Integer> {

        @Override
        protected void onPreExecute() {
            setProgressBarIndeterminateVisibility(true);
        }

        @Override
        protected Integer doInBackground(String... params) {
            InputStream inputStream = null;
            Integer result = 0;
            HttpURLConnection urlConnection = null;

            try {
                /* forming th java.net.URL object */
                URL url = new URL(params[0]);

                urlConnection = (HttpURLConnection) url.openConnection();

                /* for Get request */
                urlConnection.setRequestMethod("GET");

                int statusCode = urlConnection.getResponseCode();

                /* 200 represents HTTP OK */
                if (statusCode ==  200) {

                    BufferedReader r = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
                    StringBuilder response = new StringBuilder();
                    String line;
                    while ((line = r.readLine()) != null) {
                        response.append(line);
                    }

                    parseResult(response.toString());
                    result = 1; // Successful
                }else{
                    result = 0; //"Failed to fetch data!";
                }

            } catch (Exception e) {
                Log.d(TAG, e.getLocalizedMessage());
            }

            return result; //"Failed to fetch data!";
        }

        @Override
        protected void onPostExecute(Integer result) {

            setProgressBarIndeterminateVisibility(false);

            /* Download complete. Lets update UI */
            if (result == 1) {
                adapter = new MyRecyclerAdapter(FeedListActivity.this, feedItemList);
                mRecyclerView.setAdapter(adapter);
            } else {
                Log.e(TAG, "Failed to fetch data!");
            }
        }
    }

    private void parseResult(String result) {
        try {
            JSONObject response = new JSONObject(result);
            JSONArray posts = response.optJSONArray("category");

            /*Initialize array if null*/
            if (null == feedItemList) {
                feedItemList = new ArrayList<FeedItem>();
            }

            for (int i = 0; i < posts.length(); i++) {
                JSONObject post = posts.optJSONObject(i);

                FeedItem item = new FeedItem();
                item.setTitle(post.optString("item_name"));
                item.setThumbnail(post.optString("item_image"));
                item.setDescription(post.optString("description"));
                feedItemList.add(item);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

}