Skip to content

Instantly share code, notes, and snippets.

@alexnitu88
Created June 3, 2016 09:36
Show Gist options
  • Save alexnitu88/8c86419ff1e0e170d49f9b6043470e39 to your computer and use it in GitHub Desktop.
Save alexnitu88/8c86419ff1e0e170d49f9b6043470e39 to your computer and use it in GitHub Desktop.
RecyclerView with pagination
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="64dp"
android:elevation="3dp"
android:orientation="vertical"
app:cardElevation="3dp">
<ProgressBar
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center"/>
</android.support.v7.widget.CardView>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="vertical">
<ImageView
android:id="@+id/station_iv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@drawable/background_news_list_item_details"
android:padding="16dp">
<ro.rompetrol.mobileapp.ui.TextView
android:id="@+id/station_name_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rompetrol Viilor"
android:textColor="@android:color/white"
android:textSize="20sp"
custom:font="roboto_regular"/>
<ProgressBar
android:id="@+id/direction_progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/distance_tv"
android:layout_alignTop="@id/distance_tv"
android:indeterminate="true"/>
<ro.rompetrol.mobileapp.ui.TextView
android:id="@+id/distance_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/station_name_tv"
android:text="2 KM"
android:textColor="@android:color/white"
custom:font="roboto_condensed"
/>
<ro.rompetrol.mobileapp.ui.TextView
android:id="@+id/eta_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/station_name_tv"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/distance_tv"
android:text="2 ore 33 min"
android:textColor="@android:color/white"
custom:font="roboto_condensed"/>
</RelativeLayout>
</RelativeLayout>
import android.content.Context;
import android.content.Intent;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.akexorcist.googledirection.DirectionCallback;
import com.akexorcist.googledirection.GoogleDirection;
import com.akexorcist.googledirection.constant.RequestResult;
import com.akexorcist.googledirection.constant.TransportMode;
import com.akexorcist.googledirection.constant.Unit;
import com.akexorcist.googledirection.model.Direction;
import com.akexorcist.googledirection.model.Leg;
import com.bumptech.glide.Glide;
import com.google.android.gms.maps.model.LatLng;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import butterknife.Bind;
import butterknife.ButterKnife;
import ro.rompetrol.mobileapp.LocationService;
import ro.rompetrol.mobileapp.R;
import ro.rompetrol.mobileapp.models.StationModel;
import ro.rompetrol.mobileapp.utils.DirectionsHelper;
import rx.Observer;
/**
* Created by Alex Nitu on 4/14/2016.
*/
public class StationListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static final String TAG = StationListAdapter.class.getSimpleName();
public static final int STATION_ITEM_VIEW = 0;
public static final int LOADING_ITEM_VIEW = 1;
public static final String LANGUAGE_ROMANIAN = "ro";
private Context mContext;
private int mListItemHeight;
private List<StationModel> mItems = new ArrayList<>();
private LatLng mMyLastLocation;
private boolean mIsLoadingFooterAdded;
private DirectionsHelper mDirectionsHelper = new DirectionsHelper();
private Map<StationViewHolder, Observer> mRequestMap = new HashMap<>();
public StationListAdapter(Context context) {
mContext = context;
mMyLastLocation = LocationService.mLastLocation;
}
public void setItems(List<StationModel> items) {
mItems = items;
}
public void addAll(Collection<StationModel> items) {
mItems.addAll(items);
}
public void animateTo(List<StationModel> models) {
applyAndAnimateRemovals(models);
applyAndAnimateAdditions(models);
applyAndAnimateMovedItems(models);
}
public StationModel removeItem(int position) {
final StationModel model = mItems.remove(position);
notifyItemRemoved(position);
return model;
}
public void addItem(int position, StationModel model) {
mItems.add(position, model);
notifyItemInserted(position);
}
public void moveItem(int fromPosition, int toPosition) {
final StationModel model = mItems.remove(fromPosition);
mItems.add(toPosition, model);
notifyItemMoved(fromPosition, toPosition);
}
public void clear() {
mItems.clear();
mIsLoadingFooterAdded = false;
notifyDataSetChanged();
}
public void addLoadingFooter() {
mIsLoadingFooterAdded = true;
addItem(mItems.size(), new StationModel());
}
public void removeLoadingFooter() {
if (!mIsLoadingFooterAdded) {
return;
}
mIsLoadingFooterAdded = false;
int position = mItems.size() - 1;
mItems.remove(position);
notifyItemRemoved(position);
}
public void setListItemHeight(int listItemHeight) {
mListItemHeight = listItemHeight;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(mContext);
switch (viewType) {
case STATION_ITEM_VIEW:
View itemView = inflater.inflate(R.layout.list_item_station, parent, false);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) itemView.getLayoutParams();
lp.height = mListItemHeight;
return new StationViewHolder(itemView);
case LOADING_ITEM_VIEW:
itemView = inflater.inflate(R.layout.list_item_loading, parent, false);
return new LoadingViewHolder(itemView);
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case STATION_ITEM_VIEW:
StationModel item = mItems.get(position);
StationViewHolder svh = (StationViewHolder) holder;
svh.mStationNameTv.setText(item.getName());
// svh.mDistanceTv.setText(item.getDistance());
//holder.mEtaTv.setText("");
Glide.with(mContext)
.load("")
.placeholder(R.drawable.ic_station_placeholder)
.centerCrop()
.into(svh.mStationIv);
String distance = item.getDistance();
if (TextUtils.isEmpty(distance)) {
requestDistance(item, svh);
svh.mDirectionsPb.setVisibility(View.VISIBLE);
svh.mDistanceTv.setVisibility(View.INVISIBLE);
svh.mEtaTv.setVisibility(View.INVISIBLE);
} else {
svh.mDirectionsPb.setVisibility(View.INVISIBLE);
svh.mDistanceTv.setVisibility(View.VISIBLE);
svh.mEtaTv.setVisibility(View.VISIBLE);
svh.mDistanceTv.setText(distance);
svh.mEtaTv.setText(item.getEta());
}
break;
case LOADING_ITEM_VIEW:
LoadingViewHolder lvh = (LoadingViewHolder) holder;
break;
}
}
@Override
public int getItemViewType(int position) {
return ((position == (mItems.size() - 1)) && mIsLoadingFooterAdded) ? LOADING_ITEM_VIEW : STATION_ITEM_VIEW;
}
@Override
public int getItemCount() {
return mItems != null ? mItems.size() : 0;
}
private void applyAndAnimateRemovals(List<StationModel> newModels) {
for (int i = mItems.size() - 1; i >= 0; i--) {
final StationModel model = mItems.get(i);
if (!newModels.contains(model)) {
removeItem(i);
}
}
}
private void applyAndAnimateAdditions(List<StationModel> newModels) {
for (int i = 0, count = newModels.size(); i < count; i++) {
final StationModel model = newModels.get(i);
if (!mItems.contains(model)) {
addItem(i, model);
}
}
}
private void applyAndAnimateMovedItems(List<StationModel> newModels) {
for (int toPosition = newModels.size() - 1; toPosition >= 0; toPosition--) {
final StationModel model = newModels.get(toPosition);
final int fromPosition = mItems.indexOf(model);
if (fromPosition >= 0 && fromPosition != toPosition) {
moveItem(fromPosition, toPosition);
}
}
}
private void requestDistance(final StationModel stationModel, final StationViewHolder svh) {
if (TextUtils.isEmpty(stationModel.getLng()) || TextUtils.isEmpty(stationModel.getLat())) {
svh.mDistanceTv.setText("-");
svh.mEtaTv.setText("");
return;
}
final Observer<Direction> observer = new Observer<Direction>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "getDirections error");
svh.mDistanceTv.setText("-");
svh.mEtaTv.setText("-");
}
@Override
public void onNext(Direction direction) {
Log.d(TAG, "getDirections success");
String status = direction.getStatus();
String distance = "";
String eta = "";
if (status.equals(RequestResult.OK)) {
Leg mainLeg = direction.getRouteList().get(0).getLegList().get(0);
eta = mainLeg.getDuration().getText();
distance = mainLeg.getDistance().getText();
}
stationModel.setDistance(distance);
stationModel.setEta(eta);
boolean isViewStillVisible = mRequestMap.get(svh) == this;
if (isViewStillVisible) {
svh.mEtaTv.setVisibility(View.VISIBLE);
svh.mDistanceTv.setVisibility(View.VISIBLE);
svh.mDirectionsPb.setVisibility(View.INVISIBLE);
svh.mDistanceTv.setText(!TextUtils.isEmpty(distance) ? distance : "-");
svh.mEtaTv.setText(!TextUtils.isEmpty(eta) ? eta : "");
}
mRequestMap.remove(svh);
}
};
mRequestMap.put(svh, observer);
mDirectionsHelper.calculateDistance(new LatLng(44.426767, 26.102538),
new LatLng(Double.parseDouble(stationModel.getLat()), Double.parseDouble(stationModel.getLng())),
observer);
}
class StationViewHolder extends RecyclerView.ViewHolder {
UUID mUUID = UUID.randomUUID();
@Bind(R.id.station_iv) ImageView mStationIv;
@Bind(R.id.station_name_tv) TextView mStationNameTv;
@Bind(R.id.direction_progressBar) ProgressBar mDirectionsPb;
@Bind(R.id.distance_tv) TextView mDistanceTv;
@Bind(R.id.eta_tv) TextView mEtaTv;
public StationViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
itemView.setOnClickListener(mOnClickListener);
}
View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().postSticky(mItems.get(getAdapterPosition()));
mContext.startActivity(new Intent(mContext, StationDetailsActivity.class));
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StationViewHolder that = (StationViewHolder) o;
return mUUID != null ? mUUID.equals(that.mUUID) : that.mUUID == null;
}
@Override
public int hashCode() {
return mUUID != null ? mUUID.hashCode() : 0;
}
}
class LoadingViewHolder extends RecyclerView.ViewHolder {
View mItemView;
public LoadingViewHolder(View itemView) {
super(itemView);
mItemView = itemView;
}
}
}
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import java.util.ArrayList;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import ro.rompetrol.mobileapp.BaseFragment;
import ro.rompetrol.mobileapp.R;
import ro.rompetrol.mobileapp.models.StationModel;
import ro.rompetrol.mobileapp.utils.UIUtil;
import rx.functions.Action1;
/**
* Created by Alex Nitu on 5/13/2016.
*/
public class StationListFragment extends BaseFragment {
public static final String TAG = StationListFragment.class.getSimpleName();
public static final String EXTRA_STATION_TYPE_ID = "station_type_id";
@Bind(R.id.progressBar) ProgressBar mProgressBar;
@Bind(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
@Bind(R.id.stations_rv) RecyclerView mStationsRv;
private LinearLayoutManager mLayoutManager;
private StationListAdapter mAdapter;
private StationPagerAdapter mVpAdapter;
private String mStationTypeId;
private List<StationModel> mItems = new ArrayList<>();
public static StationListFragment newInstance() {
StationListFragment fragment = new StationListFragment();
return fragment;
}
public static StationListFragment newInstance(String typeId) {
Bundle args = new Bundle();
args.putString(EXTRA_STATION_TYPE_ID, typeId);
StationListFragment fragment = new StationListFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
mStationTypeId = args.getString(EXTRA_STATION_TYPE_ID);
}
}
//TODO handle no internet loading stuck
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_station_list, container, false);
return view;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
mLayoutManager = new LinearLayoutManager(getActivity());
mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mAdapter = new StationListAdapter(getActivity());
mAdapter.setListItemHeight(getListItemHeight());
mStationsRv.setHasFixedSize(true);
mStationsRv.setLayoutManager(mLayoutManager);
mStationsRv.addOnScrollListener(mRecyclerViewOnScrollListener);
mStationsRv.setAdapter(mAdapter);
mSwipeRefreshLayout.setOnRefreshListener(mOnRefreshListener);
mProgressBar.setVisibility(View.VISIBLE);
getStations();
}
private void getStations() {
mStationHelper.stations(mStationTypeId, null, new Action1<List<StationModel>>() {
@Override
public void call(List<StationModel> stationModels) {
mProgressBar.setVisibility(View.GONE);
mSwipeRefreshLayout.setRefreshing(false);
mItems.addAll(stationModels);
mAdapter.animateTo(mItems);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
mProgressBar.setVisibility(View.GONE);
mSwipeRefreshLayout.setRefreshing(false);
}
});
}
private void loadMoreItems() {
mAdapter.addLoadingFooter();
mStationHelper.stations(mStationTypeId, null, new Action1<List<StationModel>>() {
@Override
public void call(List<StationModel> stationModels) {
mAdapter.removeLoadingFooter();
mItems.addAll(stationModels);
mAdapter.animateTo(mItems);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
mAdapter.removeLoadingFooter();
//TODO
}
});
}
private int getListItemHeight() {
//Height has to be 1/3 of available space
//(Total height - (StatusBar height + ActionBar height)) / 3
return (int) ((UIUtil.getDisplayHeight(getActivity()) - UIUtil.convertDpToPixel(56 + 24)) / 3);
}
private RecyclerView.OnScrollListener mRecyclerViewOnScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = mLayoutManager.getChildCount();
int totalItemCount = mLayoutManager.getItemCount();
int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
if (mStationHelper.isLoading()) {
return;
}
if (((visibleItemCount + firstVisibleItemPosition) >= totalItemCount)
&& (firstVisibleItemPosition >= 0)
&& (totalItemCount >= StationHelper.PAGE_SIZE)) {
loadMoreItems();
}
}
};
private SwipeRefreshLayout.OnRefreshListener mOnRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
mItems.clear();
mAdapter.clear();
getStations();
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment