Initial interface development

This commit is contained in:
Tariel Hlontsi 2016-05-03 12:40:48 +03:00
parent feee5f20e9
commit 501a3f80ca
47 changed files with 1437 additions and 307 deletions

View File

@ -10,13 +10,11 @@ import android.util.Log;
import hikapro.com.backpack.model.DetailModel;
import hikapro.com.backpack.model.ItemModel;
import hikapro.com.backpack.model.SetModel;
import hikapro.com.backpack.model.database.DAO;
import hikapro.com.backpack.model.entities.Item;
import hikapro.com.backpack.model.entities.Set;
import hikapro.com.backpack.presenter.ItemDetailPresenter;
import hikapro.com.backpack.presenter.ItemListPresenter;
import hikapro.com.backpack.presenter.SetListPresenter;
import hikapro.com.backpack.presenter.adapters.helper.OnStartDragListener;
import hikapro.com.backpack.view.View;
import hikapro.com.backpack.view.fragments.ItemDetailFragment;
import hikapro.com.backpack.view.fragments.ItemListFragment;

View File

@ -9,16 +9,12 @@ import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import hikapro.com.backpack.model.database.Command;
import hikapro.com.backpack.model.database.DAO;
import hikapro.com.backpack.model.database.Event;
import hikapro.com.backpack.model.dao.Command;
import hikapro.com.backpack.model.dao.DAO;
import hikapro.com.backpack.model.dao.Event;
import hikapro.com.backpack.model.entities.Category;
import hikapro.com.backpack.model.entities.Item;
import hikapro.com.backpack.model.entities.Set;
import hikapro.com.backpack.presenter.Presenter;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Created by tariel on 22/04/16.
@ -29,21 +25,27 @@ public class ItemModel implements Model.Item {
private List<Category> rawCategories;
private List<Category> sortedCategories;
private List<Item> rawItems;
private DAO dao;
private int currentSet;
private Category currentCategory;
private List<Category> categoriesCache;
private int currentSet;
private Hashtable<Integer, Category> categoriesCache;
private List<Item> itemsCache;
private List<Item> itemsDiscardCache;
private Hashtable<Category, List<Item>> items;
private Hashtable<Integer, Hashtable<Category, List<Item>>> cache;
private Hashtable<Integer, List<Item>> cache;
public ItemModel() {
this.rawCategories = new ArrayList<>();
this.rawItems = new ArrayList<>();
this.sortedCategories = new ArrayList<>();
this.categoriesCache = new ArrayList<>(20);
this.categoriesCache = new Hashtable<>(20, 0.9f);
this.itemsCache = new ArrayList<>();
this.itemsDiscardCache = new ArrayList<>();
this.cache = new Hashtable<>(12, 0.9f);
this.dao = DAO.getInstance();
this.dao = DAO.getInstance();
dao.registerObserver(this);
}
@ -51,8 +53,11 @@ public class ItemModel implements Model.Item {
@Override
public Category getCategoryByPosition(int position) {
currentCategory = categoriesCache.get(position);
return currentCategory;
Category ret = null;
if (cache.containsKey(currentSet))
ret = categoriesCache.get(cache.get(currentSet).get(position).getCategory());
return ret;
}
@Override
public int getCategoriesCount() {
@ -65,12 +70,23 @@ public class ItemModel implements Model.Item {
@Override
public int insertItem(Item item) {
List<Item> iList = cache.get(currentSet);
iList.add(item);
Collections.sort(iList);
cache.put(currentSet, iList);
// TODO write to database
return 0;
}
@Override
public boolean deleteItem(int id) {
public boolean deleteItem(Item item) {
if (isPendingRemoval(item))
itemsDiscardCache.remove(item);
cache.get(currentSet).remove(item);
//TODO write to ds
return false;
}
@Override
public Item findItem(int id) {
Item item = null;
@ -83,15 +99,75 @@ public class ItemModel implements Model.Item {
return item;
}
@Override
public Item getItemByPosition(int categoryId, int position) {
Item ret = cache.get(currentSet).get(currentCategory).get(position);
public Item getItemByPosition(int position) {
Item ret = null;
if (cache.containsKey(currentSet))
ret = cache.get(currentSet).get(position);
return ret;
}
@Override
public int getItemsCount(int categoryId) {
int ret = cache.get(currentSet).get(currentCategory).size();
return ret;
public void filter(String query) {
if (query.isEmpty()) {
Message command = Message.obtain();
command.what = Command.SET_GET_ITEMS;
command.arg1 = presenter.getCurrentSet().getId();
dao.executeCommand(command);
} else {
query = query.toLowerCase();
String name;
List<Item> newList = new ArrayList<>(20);
List<Item> oldList = cache.get(currentSet);
for (Item item : oldList) {
name = item.getName().toLowerCase();
if (name.contains(query)) {
newList.add(item);
}
}
cache.put(currentSet, newList);
}
}
@Override
public int getHeaderId(int position) {
return cache.containsKey(currentSet) ? cache.get(currentSet).get(position).getCategory() : -1;
}
@Override
public int getItemId(int position) {
return cache.containsKey(currentSet) ? cache.get(currentSet).get(position).getId() : -1;
}
@Override
public void clear() {
if (cache.containsKey(currentSet))
cache.get(currentSet).clear();
}
@Override
public boolean isPendingRemoval(Item item) {
return itemsDiscardCache.contains(item);
}
@Override
public void pendingRemoveCancel(Item item) {
if (itemsDiscardCache.contains(item))
itemsDiscardCache.remove(item);
}
@Override
public void pendingRemove(Item item) {
itemsDiscardCache.add(item);
}
@Override
public int getItemsCount() {
boolean is = cache.containsKey(currentSet);
return is ? cache.get(currentSet).size() : 0;
}
// items <--
// events -->
@ -117,17 +193,21 @@ public class ItemModel implements Model.Item {
@Override
public void executeQuery() {
Message command;
if (cache.contains(currentSet)) {
Hashtable<Category, List<Item>> buff = cache.get(currentSet);
Category[] array = buff.keySet().toArray(new Category[buff.keySet().size()]);
categoriesCache = Arrays.asList(array);
notifyDataSetChanged();
} else {
Message command = Message.obtain();
if (categoriesCache.isEmpty()) {
command = Message.obtain();
command.what = Command.ITEM_GET_CATEGORIES;
dao.executeCommand(command);
}
command = Message.obtain();
command.what = Command.SET_GET_ITEMS;
command.arg1 = presenter.getCurrentSet().getId();
dao.executeCommand(command);
}
}
@Override
@ -146,13 +226,16 @@ public class ItemModel implements Model.Item {
break;
case Event.ITEM_INSERT_ERROR :
break;
case Event.ITEM_CATEGORY_LOAD_ERROR :
break;
case Event.SET_ITEMS_LOAD_COMPLETED :
Hashtable<Category, List<Item>> res = (Hashtable<Category, List<Item>>) event.obj;
List<Item> res = (List<Item>) event.obj;
cache.put(event.arg1, res);
Category[] array = res.keySet().toArray(new Category[res.keySet().size()]);
categoriesCache = Arrays.asList(array);
notifyDataSetChanged();
break;
case Event.ITEM_CATEGORY_LOAD_COMPLETED :
categoriesCache = (Hashtable<Integer, Category>)event.obj;
break;
case Event.ITEM_FROM_SET_DELETED :
break;
case Event.ITEM_DELETED :
@ -220,7 +303,6 @@ public class ItemModel implements Model.Item {
}
}
return category;
}
private Category findCategory(int categoryId) {
Category category = null;

View File

@ -34,10 +34,20 @@ public interface Model {
interface Item extends Base {
int insertItem(hikapro.com.backpack.model.entities.Item item);
boolean deleteItem(int id);
boolean deleteItem(hikapro.com.backpack.model.entities.Item item);
void filter(String query);
int getHeaderId(int position);//TODO review
int getItemId(int position);//TODO review
void clear();
boolean isPendingRemoval(hikapro.com.backpack.model.entities.Item item);
void pendingRemove(hikapro.com.backpack.model.entities.Item item);
void pendingRemoveCancel(hikapro.com.backpack.model.entities.Item item);
int getItemsCount();
hikapro.com.backpack.model.entities.Item findItem(int id);
hikapro.com.backpack.model.entities.Item getItemByPosition(int categoryId, int position);
int getItemsCount(int categoryId);
hikapro.com.backpack.model.entities.Item getItemByPosition(int position);
hikapro.com.backpack.model.entities.Category getCategoryByPosition(int position);
int getCategoriesCount();
void setPresenter(Presenter.ItemList presenter);

View File

@ -5,9 +5,9 @@ import android.os.Message;
import java.util.ArrayList;
import java.util.List;
import hikapro.com.backpack.model.database.Command;
import hikapro.com.backpack.model.database.DAO;
import hikapro.com.backpack.model.database.Event;
import hikapro.com.backpack.model.dao.Command;
import hikapro.com.backpack.model.dao.DAO;
import hikapro.com.backpack.model.dao.Event;
import hikapro.com.backpack.model.entities.Set;
import hikapro.com.backpack.presenter.Presenter;

View File

@ -1,4 +1,4 @@
package hikapro.com.backpack.model.database;
package hikapro.com.backpack.model.dao;
/**
* Created by tariel on 27/04/16.
@ -18,6 +18,7 @@ public interface Command {
int ITEM_INSERT = 0x79;
int ITEM_PACK = 0x7A;
int ITEM_UNPACK = 0x7B;
int ITEM_GET_CATEGORIES = 0x7C;
int MY_LIST_POST = 0x8C;
int MY_LIST_ITEM_ADD = 0x8D;

View File

@ -1,4 +1,4 @@
package hikapro.com.backpack.model.database;
package hikapro.com.backpack.model.dao;
import android.content.ContentValues;
import android.content.Context;
@ -132,6 +132,12 @@ public class DAO {
threadPool.execute(setTask);
break;
case Command.ITEM_GET_CATEGORIES :
itemTask = new ItemTask(Command.ITEM_GET_CATEGORIES,
Process.THREAD_PRIORITY_MORE_FAVORABLE);
threadPool.execute(itemTask);
break;
case Command.ITEM_INSERT :
itemTask = new ItemTask(Command.ITEM_INSERT,
Process.THREAD_PRIORITY_BACKGROUND);
@ -590,6 +596,16 @@ public class DAO {
else
message.what = Event.ITEM_UNPACK_ERROR;
break;
case Command.ITEM_GET_CATEGORIES :
Hashtable<Integer, Category> res = readCategories();
if (res.isEmpty())
message.what = Event.ITEM_CATEGORY_LOAD_ERROR;
else {
message.what = Event.ITEM_CATEGORY_LOAD_COMPLETED;
message.obj = res;
}
break;
}
handler.sendMessage(message);
}
@ -630,22 +646,8 @@ public class DAO {
message.what = Event.SET_ITEMS_LOAD_ERROR;
else {
Collections.sort(items);
Hashtable<Integer, Category> categories = readCategories();
Hashtable<Category, List<Item>> result = new Hashtable<>(20, 0.9f);
Category category;
for (Item item : items) {
category = categories.get(item.getCategory());
if (result.containsKey(category)) {
result.get(category).add(item);
} else {
List<Item> innerList = new ArrayList<>(20);
innerList.add(item);
result.put(category, innerList);
}
}
message.what = Event.SET_ITEMS_LOAD_COMPLETED;
message.obj = result;
message.obj = items;
message.arg1 = setId;
}
break;

View File

@ -1,8 +1,7 @@
package hikapro.com.backpack.model.database;
package hikapro.com.backpack.model.dao;
import android.content.ContentValues;
import android.database.Cursor;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

View File

@ -1,4 +1,4 @@
package hikapro.com.backpack.model.database;
package hikapro.com.backpack.model.dao;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;

View File

@ -1,4 +1,4 @@
package hikapro.com.backpack.model.database;
package hikapro.com.backpack.model.dao;
/**
* Created by tariel on 27/04/16.
@ -24,12 +24,14 @@ public interface Event {
int ITEM_DELETE_ERROR = -0x16;
int ITEM_PACK_ERROR = -0x17;
int ITEM_UNPACK_ERROR = -0x18;
int ITEM_CATEGORY_LOAD_ERROR = -0x19;
int ITEM_FROM_SET_DELETED = 0x14;
int ITEM_INSERTED = 0x15;
int ITEM_DELETED = 0x16;
int ITEM_PACKED = 0x17;
int ITEM_UNPACKED = 0x18;
int ITEM_CATEGORY_LOAD_COMPLETED = 0x19;
int MY_LIST_POST_ERROR = -0x28;
int MY_LIST_ITEM_ADD_ERROR = -0x29;

View File

@ -1,4 +1,4 @@
package hikapro.com.backpack.model.database;
package hikapro.com.backpack.model.dao;
/**
* Created by tariel on 27/04/16.

View File

@ -2,27 +2,24 @@ package hikapro.com.backpack.presenter;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Toast;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import hikapro.com.backpack.R;
import hikapro.com.backpack.model.ItemModel;
import hikapro.com.backpack.model.Model;
import hikapro.com.backpack.model.entities.Item;
import hikapro.com.backpack.model.entities.Set;
import hikapro.com.backpack.presenter.adapters.CategoryListAdapter;
import hikapro.com.backpack.presenter.adapters.helper.items.DividerDecoration;
import hikapro.com.backpack.presenter.adapters.ItemListAdapter;
import hikapro.com.backpack.presenter.adapters.helper.items.ItemSwipeCallback;
import hikapro.com.backpack.presenter.adapters.helper.items.StickyHeaderDecoration;
import hikapro.com.backpack.view.View;
import hikapro.com.backpack.view.recycler.CategoryViewHolder;
import hikapro.com.backpack.view.recycler.ItemViewHolder;
/**
* Created by tariel on 20/04/16.
@ -34,10 +31,12 @@ public class ItemListPresenter implements Presenter.ItemList {
private WeakReference<View.ItemList> view;
private Model.Item model;
private Set set;
private CategoryListAdapter categoryListAdapter;
private ItemListAdapter adapter;
RecyclerView recycler;
public ItemListPresenter() {
this.categoryListAdapter = new CategoryListAdapter(this);
this.adapter = new ItemListAdapter(this);
adapter.setHasStableIds(true);
}
// life cycle -->
@ -56,10 +55,25 @@ public class ItemListPresenter implements Presenter.ItemList {
set = (Set) savedInstanceState.getSerializable(BUNDLE_SET_LIST_KEY);
android.view.View view = inflater.inflate(R.layout.fragment_item_list, container, false);
LinearLayoutManager llm = new LinearLayoutManager(getActivityContext());
RecyclerView mainRecycler = (RecyclerView) view.findViewById(R.id.categories_main_recycler);
mainRecycler.setLayoutManager(llm);
mainRecycler.setAdapter(categoryListAdapter);
mainRecycler.setItemAnimator(new DefaultItemAnimator());
recycler = (RecyclerView) view.findViewById(R.id.items_recycler);
recycler.setLayoutManager(llm);
recycler.setAdapter(adapter);
final StickyHeaderDecoration decoration = new StickyHeaderDecoration(adapter);
recycler.addItemDecoration(decoration);
recycler.addItemDecoration(new DividerDecoration(getActivityContext()));
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
decoration.invalidateHeaders();
}
});
ItemSwipeCallback itemSwipeCallback = new ItemSwipeCallback(0, ItemTouchHelper.LEFT, adapter, getActivityContext());
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemSwipeCallback);
itemTouchHelper.attachToRecyclerView(recycler);
adapter.setUndoOn(true);
model.executeQuery();
return view;
}
@ -71,68 +85,11 @@ public class ItemListPresenter implements Presenter.ItemList {
// life cycle <--
// recycler -->
@Override
public CategoryViewHolder createViewHolderCategory(ViewGroup parent, int viewType) {
CategoryViewHolder viewHolder;
android.view.View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.section,
parent, false);
viewHolder = new CategoryViewHolder(v);
return viewHolder;
}
@Override
public void bindViewHolderCategory(CategoryViewHolder holder, int position) {
final hikapro.com.backpack.model.entities.Category category = model.getCategoryByPosition(position);
holder.sectionText.setText(category.getName());
LinearLayoutManager llm = new LinearLayoutManager(getActivityContext());
holder.itemsRecycler.setLayoutManager(llm);
ItemListAdapter itemListAdapter = new ItemListAdapter(this);
itemListAdapter.setCategoryId(category.getId());
holder.itemsRecycler.setAdapter(itemListAdapter);
holder.itemsRecycler.setItemAnimator(new DefaultItemAnimator());
categoryListAdapter.addItemAdapter(itemListAdapter);
}
@Override
public ItemViewHolder createViewHolderItem(ViewGroup parent, int viewType) {
ItemViewHolder viewHolder;
android.view.View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item,
parent, false);
viewHolder = new ItemViewHolder(v);
return viewHolder;
}
@Override
public void bindViewHolderItem(final ItemViewHolder holder, final int position, int categoryId) {
hikapro.com.backpack.model.entities.Item item = model.getItemByPosition(categoryId, position);
holder.title.setText(item.getName());
holder.id = item.getId();
holder.title.setOnClickListener(new android.view.View.OnClickListener() {
@Override
public void onClick(android.view.View view) {
clickItem(holder.id);
}
});
}
@Override
public int getItemsCount(int categoryId) {
return model.getItemsCount(categoryId);//TODO category Id
}
@Override
public int getCategoriesCount() {
return model.getCategoriesCount();
}
// recycler <--
// process -->
@Override
public void notifyDataSetChanged() {
categoryListAdapter.notifyDataSetChanged();
categoryListAdapter.notifyItemAdapters();
adapter.notifyDataSetChanged();
}
@Override
@ -156,6 +113,12 @@ public class ItemListPresenter implements Presenter.ItemList {
public void setModel(Model.Item model) {
this.model = model;
}
@Override
public Model.Item getModel() {
return model;
}
@Override
public Context getAppContext() {
try {
@ -186,6 +149,11 @@ public class ItemListPresenter implements Presenter.ItemList {
throw new NullPointerException("View is unavailable");
}
@Override
public void filter(String query) {
adapter.filter(query);
recycler.scrollToPosition(0);
}
@Override
public Set getCurrentSet() {

View File

@ -3,15 +3,12 @@ package hikapro.com.backpack.presenter;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.DragEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import hikapro.com.backpack.model.Model;
import hikapro.com.backpack.model.entities.Item;
import hikapro.com.backpack.model.entities.Set;
import hikapro.com.backpack.view.recycler.CategoryViewHolder;
import hikapro.com.backpack.view.recycler.DetailViewHolder;
import hikapro.com.backpack.view.recycler.ItemViewHolder;
import hikapro.com.backpack.view.recycler.SetViewHolder;
@ -45,21 +42,18 @@ public interface Presenter {
}
interface ItemList extends Base {
ItemViewHolder createViewHolderItem(ViewGroup parent, int viewType);
void bindViewHolderItem(ItemViewHolder holder, int position, int categoryId);
int getItemsCount(int categoryId);
CategoryViewHolder createViewHolderCategory(ViewGroup parent, int viewType);
void bindViewHolderCategory(CategoryViewHolder holder, int position);
int getCategoriesCount();
void onDestroy(boolean isChangingConfiguration);
android.view.View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
void setView(hikapro.com.backpack.view.View.ItemList view);
void setModel(Model.Item model);
Model.Item getModel();
void notifyDataSetChanged();
Set getCurrentSet();
void showMessage(String message);
void onSaveInstanceState(Bundle outState);
void clickItem(int itemId);
void filter(String query);
}
interface ItemDetail extends Base {

View File

@ -17,8 +17,8 @@ import hikapro.com.backpack.R;
import hikapro.com.backpack.model.Model;
import hikapro.com.backpack.model.entities.Set;
import hikapro.com.backpack.presenter.adapters.SetListAdapter;
import hikapro.com.backpack.presenter.adapters.helper.OnStartDragListener;
import hikapro.com.backpack.presenter.adapters.helper.SimpleItemTouchHelperCallback;
import hikapro.com.backpack.presenter.adapters.helper.sets.OnStartDragListener;
import hikapro.com.backpack.presenter.adapters.helper.sets.SimpleItemTouchHelperCallback;
import hikapro.com.backpack.view.View;
import hikapro.com.backpack.view.recycler.SetViewHolder;

View File

@ -1,50 +0,0 @@
package hikapro.com.backpack.presenter.adapters;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
import hikapro.com.backpack.presenter.Presenter;
import hikapro.com.backpack.view.recycler.CategoryViewHolder;
/**
* Created by tariel on 20/04/16.
*/
public class CategoryListAdapter extends RecyclerView.Adapter<CategoryViewHolder> {
private Presenter.ItemList presenter;
private List<ItemListAdapter> itemAdapters;
public CategoryListAdapter(Presenter.ItemList presenter) {
this.presenter = presenter;
this.itemAdapters = new ArrayList<>();
}
@Override
public int getItemCount() {
return presenter.getCategoriesCount();
}
@Override
public void onBindViewHolder(CategoryViewHolder holder, int position) {
presenter.bindViewHolderCategory(holder, position);
}
@Override
public CategoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return presenter.createViewHolderCategory(parent, viewType);
}
public void addItemAdapter(ItemListAdapter adapter) {
itemAdapters.add(adapter);
adapter.notifyDataSetChanged();
}
public void notifyItemAdapters() {
for (ItemListAdapter adapter : itemAdapters) {
adapter.notifyDataSetChanged();
}
}
}

View File

@ -1,18 +1,35 @@
package hikapro.com.backpack.presenter.adapters;
import android.graphics.Color;
import android.os.Handler;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.HashMap;
import hikapro.com.backpack.R;
import hikapro.com.backpack.model.Model;
import hikapro.com.backpack.model.entities.Category;
import hikapro.com.backpack.model.entities.Item;
import hikapro.com.backpack.presenter.ItemListPresenter;
import hikapro.com.backpack.presenter.Presenter;
import hikapro.com.backpack.presenter.adapters.helper.items.StickyHeaderAdapter;
import hikapro.com.backpack.view.recycler.HeaderViewHolder;
import hikapro.com.backpack.view.recycler.ItemViewHolder;
/**
* Created by tariel on 20/04/16.
* Created by tariel on 01/05/16.
*/
public class ItemListAdapter extends RecyclerView.Adapter<ItemViewHolder> {
public class ItemListAdapter extends RecyclerView.Adapter<ItemViewHolder> implements StickyHeaderAdapter<HeaderViewHolder> {
private static final int PENDING_REMOVAL_TIMEOUT = 4000; // 4sec
boolean undoOn; // is undo on, you can turn it on from the toolbar menu
private Handler handler = new Handler(); // hanlder for running delayed runnables
HashMap<Item, Runnable> pendingRunables = new HashMap<>(); // map of items to pending runnables, so we can cancel a removal if need be
private Presenter.ItemList presenter;
private int categoryId;
public ItemListAdapter(Presenter.ItemList presenter) {
this.presenter = presenter;
@ -20,19 +37,145 @@ public class ItemListAdapter extends RecyclerView.Adapter<ItemViewHolder> {
@Override
public int getItemCount() {
return presenter.getItemsCount(categoryId);
int res = presenter.getModel().getItemsCount();
return res;
}
@Override
public long getItemId(int position) {
return presenter.getModel().getItemByPosition(position).getId();
}
@Override
public void onBindViewHolder(ItemViewHolder holder, final int position) {
final Item item = presenter.getModel().getItemByPosition(position);
if (presenter.getModel().isPendingRemoval(item)) {
// we need to show the "undo" state of the row
holder.itemView.setBackgroundColor(Color.RED);
holder.title.setVisibility(View.GONE);
holder.undoButton.setVisibility(View.VISIBLE);
holder.undoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// user wants to undo the removal, let's cancel the pending task
Runnable pendingRemovalRunnable = pendingRunables.get(item);
pendingRunables.remove(item);
if (pendingRemovalRunnable != null) handler.removeCallbacks(pendingRemovalRunnable);
presenter.getModel().pendingRemoveCancel(item);
// this will rebind the row in "normal" state
notifyItemChanged(position);
}
});
} else {
holder.title.setVisibility(View.VISIBLE);
holder.title.setText(item.getName());
holder.id = item.getId();
holder.categoryId = item.getCategory();
holder.itemView.setBackgroundColor(0x33FF99);
holder.undoButton.setVisibility(View.GONE);
holder.undoButton.setOnClickListener(null);
}
}
@Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return presenter.createViewHolderItem(parent, viewType);
ItemViewHolder viewHolder;
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item,
parent, false);
viewHolder = new ItemViewHolder(v);
return viewHolder;
}
public void setUndoOn(boolean undoOn) {
this.undoOn = undoOn;
}
public boolean isUndoOn() {
return undoOn;
}
public void add(Item item) {
presenter.getModel().insertItem(item);
notifyDataSetChanged();
}
public void clear() {
presenter.getModel().clear();
notifyDataSetChanged();
}
public void filter(String query) {
presenter.getModel().filter(query);
notifyDataSetChanged();
}
@Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
presenter.bindViewHolderItem(holder, position, categoryId);
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(hasStableIds);
}
public void setCategoryId(int categoryId) {
this.categoryId = categoryId;
@Override
public long getHeaderId(int position) {
/*if (position == 0) {
return -1;
} else {*/
return presenter.getModel().getHeaderId(position);
//}
}
@Override
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
HeaderViewHolder viewHolder;
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.view_header, parent, false);
viewHolder = new HeaderViewHolder(v);
return viewHolder;
}
@Override
public void onBindHeaderViewHolder(HeaderViewHolder holder, int position) {
Category category = presenter.getModel().getCategoryByPosition(position);
holder.id = category.getId();
holder.title.setText(category.getName());
holder.title.setBackgroundColor(0x2B1E15);
}
public void pendingRemoval(final int position) {
final Item item = presenter.getModel().getItemByPosition(position);
if (! presenter.getModel().isPendingRemoval(item)) {
presenter.getModel().pendingRemove(item);
// this will redraw row in "undo" state
notifyItemChanged(position);
// let's create, store and post a runnable to remove the item
Runnable pendingRemovalRunnable = new Runnable() {
@Override
public void run() {
remove(item, position);
}
};
handler.postDelayed(pendingRemovalRunnable, PENDING_REMOVAL_TIMEOUT);
pendingRunables.put(item, pendingRemovalRunnable);
}
}
public void remove(Item item, int position) {
presenter.getModel().deleteItem(item);
notifyItemRemoved(position);
}
public void remove(int position) {
Item item = presenter.getModel().getItemByPosition(position);
presenter.getModel().deleteItem(item);
notifyItemRemoved(position);
}
public boolean isPendingRemoval(int position) {
Item item = presenter.getModel().getItemByPosition(position);
return presenter.getModel().isPendingRemoval(item);
}
}

View File

@ -4,7 +4,7 @@ import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import hikapro.com.backpack.presenter.SetListPresenter;
import hikapro.com.backpack.presenter.adapters.helper.ItemTouchHelperAdapter;
import hikapro.com.backpack.presenter.adapters.helper.sets.ItemTouchHelperAdapter;
import hikapro.com.backpack.view.recycler.SetViewHolder;
/**

View File

@ -0,0 +1,45 @@
package hikapro.com.backpack.presenter.adapters.helper.items;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by tariel on 30/04/16.
*/
public class DimensionCalculator {
/**
* Populates {@link Rect} with margins for any view.
*
*
* @param margins rect to populate
* @param view for which to get margins
*/
public void initMargins(Rect margins, View view) {
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) layoutParams;
initMarginRect(margins, marginLayoutParams);
} else {
margins.set(0, 0, 0, 0);
}
}
/**
* Converts {@link ViewGroup.MarginLayoutParams} into a representative {@link Rect}.
*
* @param marginRect Rect to be initialized with margins coordinates, where
* {@link ViewGroup.MarginLayoutParams#leftMargin} is equivalent to {@link Rect#left}, etc.
* @param marginLayoutParams margins to populate the Rect with
*/
private void initMarginRect(Rect marginRect, ViewGroup.MarginLayoutParams marginLayoutParams) {
marginRect.set(
marginLayoutParams.leftMargin,
marginLayoutParams.topMargin,
marginLayoutParams.rightMargin,
marginLayoutParams.bottomMargin
);
}
}

View File

@ -0,0 +1,96 @@
package hikapro.com.backpack.presenter.adapters.helper.items;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class DividerDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
public DividerDecoration(Context context) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
}
private int getOrientation(RecyclerView parent) {
LinearLayoutManager layoutManager;
try {
layoutManager = (LinearLayoutManager) parent.getLayoutManager();
} catch (ClassCastException e) {
throw new IllegalStateException("DividerDecoration can only be used with a " +
"LinearLayoutManager.", e);
}
return layoutManager.getOrientation();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (getOrientation(parent) == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int recyclerViewTop = parent.getPaddingTop();
final int recyclerViewBottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = Math.max(recyclerViewTop, child.getBottom() + params.bottomMargin);
final int bottom = Math.min(recyclerViewBottom, top + mDivider.getIntrinsicHeight());
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int recyclerViewLeft = parent.getPaddingLeft();
final int recyclerViewRight = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = Math.max(recyclerViewLeft, child.getRight() + params.rightMargin);
final int right = Math.min(recyclerViewRight, left + mDivider.getIntrinsicHeight());
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (getOrientation(parent) == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}

View File

@ -0,0 +1,256 @@
package hikapro.com.backpack.presenter.adapters.helper.items;
import android.graphics.Rect;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
/**
* Created by tariel on 30/04/16.
*/
public class HeaderPositionCalculator {
private final StickyHeaderAdapter adapter;
private final HeaderProvider headerProvider;
private final DimensionCalculator dimensionCalculator;
/**
* The following fields are used as buffers for internal calculations. Their sole purpose is to avoid
* allocating new Rect every time we need one.
*/
private final Rect tempRect1 = new Rect();
private final Rect tempRect2 = new Rect();
public HeaderPositionCalculator(StickyHeaderAdapter adapter, HeaderProvider headerProvider,
DimensionCalculator dimensionCalculator) {
this.adapter = adapter;
this.headerProvider = headerProvider;
this.dimensionCalculator = dimensionCalculator;
}
/**
* Determines if a view should have a sticky header.
* The view has a sticky header if:
* 1. It is the first element in the recycler view
* 2. It has a valid ID associated to its position
*
* @param itemView given by the RecyclerView
* @param orientation of the Recyclerview
* @param position of the list item in question
* @return True if the view should have a sticky header
*/
public boolean hasStickyHeader(View itemView, int orientation, int position) {
int offset, margin;
dimensionCalculator.initMargins(tempRect1, itemView);
if (orientation == LinearLayout.VERTICAL) {
offset = itemView.getTop();
margin = tempRect1.top;
} else {
offset = itemView.getLeft();
margin = tempRect1.left;
}
boolean ret = offset <= margin;
ret = ret && adapter.getHeaderId(position) >= 0;
return ret;
}
/**
* Determines if an item in the list should have a header that is different than the item in the
* list that immediately precedes it. Items with no headers will always return false.
*
* @param position of the list item in questions
* @param isReverseLayout TRUE if layout manager has flag isReverseLayout
* @return true if this item has a different header than the previous item in the list
*/
public boolean hasNewHeader(int position) {
if (indexOutOfBounds(position)) {
return false;
}
long headerId = adapter.getHeaderId(position);
if (headerId < 0) {
return false;
}
long nextItemHeaderId = -1;//TODO -1
int nextItemPosition = position - 1; // TODO -1
if (!indexOutOfBounds(nextItemPosition)) {
nextItemHeaderId = adapter.getHeaderId(nextItemPosition);
}
int firstItemPosition = 0;
return position == firstItemPosition || headerId != nextItemHeaderId;
}
private boolean indexOutOfBounds(int position) {
return position < 0 || position >= adapter.getItemCount();
}
public void initHeaderBounds(Rect bounds, RecyclerView recyclerView, View header, View firstView, boolean firstHeader) {
initDefaultHeaderOffset(bounds, recyclerView, header, firstView, LinearLayout.VERTICAL);
if (firstHeader && isStickyHeaderBeingPushedOffscreen(recyclerView, header)) {
View viewAfterNextHeader = getFirstViewUnobscuredByHeader(recyclerView, header);
int firstViewUnderHeaderPosition = recyclerView.getChildAdapterPosition(viewAfterNextHeader);
View secondHeader = headerProvider.getHeader(recyclerView, firstViewUnderHeaderPosition);
translateHeaderWithNextHeader(recyclerView, LinearLayout.VERTICAL, bounds,
header, viewAfterNextHeader, secondHeader);
}
}
private void initDefaultHeaderOffset(Rect headerMargins, RecyclerView recyclerView, View header, View firstView, int orientation) {
int translationX, translationY;
dimensionCalculator.initMargins(tempRect1, header);
ViewGroup.LayoutParams layoutParams = firstView.getLayoutParams();
int leftMargin = 0;
int topMargin = 0;
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) layoutParams;
leftMargin = marginLayoutParams.leftMargin;
topMargin = marginLayoutParams.topMargin;
}
if (orientation == LinearLayoutManager.VERTICAL) {
translationX = firstView.getLeft() - leftMargin + tempRect1.left;
translationY = Math.max(
firstView.getTop() - topMargin - header.getHeight() - tempRect1.bottom,
getListTop(recyclerView) + tempRect1.top);
} else {
translationY = firstView.getTop() - topMargin + tempRect1.top;
translationX = Math.max(
firstView.getLeft() - leftMargin - header.getWidth() - tempRect1.right,
getListLeft(recyclerView) + tempRect1.left);
}
headerMargins.set(translationX, translationY, translationX + header.getWidth(),
translationY + header.getHeight());
}
private boolean isStickyHeaderBeingPushedOffscreen(RecyclerView recyclerView, View stickyHeader) {
View viewAfterHeader = getFirstViewUnobscuredByHeader(recyclerView, stickyHeader);
int firstViewUnderHeaderPosition = recyclerView.getChildAdapterPosition(viewAfterHeader);
if (firstViewUnderHeaderPosition == RecyclerView.NO_POSITION) {
return false;
}
int orientation = LinearLayoutManager.VERTICAL;
if (firstViewUnderHeaderPosition > 0 && hasNewHeader(firstViewUnderHeaderPosition)) {
View nextHeader = headerProvider.getHeader(recyclerView, firstViewUnderHeaderPosition);
dimensionCalculator.initMargins(tempRect1, nextHeader);
dimensionCalculator.initMargins(tempRect2, stickyHeader);
if (orientation == LinearLayoutManager.VERTICAL) {
int topOfNextHeader = viewAfterHeader.getTop() - tempRect1.bottom - nextHeader.getHeight() - tempRect1.top;
int bottomOfThisHeader = recyclerView.getPaddingTop() + stickyHeader.getBottom() + tempRect2.top + tempRect2.bottom;
if (topOfNextHeader < bottomOfThisHeader) {
return true;
}
} else {
int leftOfNextHeader = viewAfterHeader.getLeft() - tempRect1.right - nextHeader.getWidth() - tempRect1.left;
int rightOfThisHeader = recyclerView.getPaddingLeft() + stickyHeader.getRight() + tempRect2.left + tempRect2.right;
if (leftOfNextHeader < rightOfThisHeader) {
return true;
}
}
}
return false;
}
private void translateHeaderWithNextHeader(RecyclerView recyclerView, int orientation, Rect translation,
View currentHeader, View viewAfterNextHeader, View nextHeader) {
dimensionCalculator.initMargins(tempRect1, nextHeader);
dimensionCalculator.initMargins(tempRect2, currentHeader);
if (orientation == LinearLayoutManager.VERTICAL) {
int topOfStickyHeader = getListTop(recyclerView) + tempRect2.top + tempRect2.bottom;
int shiftFromNextHeader = viewAfterNextHeader.getTop() - nextHeader.getHeight() - tempRect1.bottom - tempRect1.top - currentHeader.getHeight() - topOfStickyHeader;
if (shiftFromNextHeader < topOfStickyHeader) {
translation.top += shiftFromNextHeader;
}
} else {
int leftOfStickyHeader = getListLeft(recyclerView) + tempRect2.left + tempRect2.right;
int shiftFromNextHeader = viewAfterNextHeader.getLeft() - nextHeader.getWidth() - tempRect1.right - tempRect1.left - currentHeader.getWidth() - leftOfStickyHeader;
if (shiftFromNextHeader < leftOfStickyHeader) {
translation.left += shiftFromNextHeader;
}
}
}
/**
* Returns the first item currently in the RecyclerView that is not obscured by a header.
*
* @param parent Recyclerview containing all the list items
* @return first item that is fully beneath a header
*/
private View getFirstViewUnobscuredByHeader(RecyclerView parent, View firstHeader) {
int step = 1;
int from = 0;
for (int i = from; i >= 0 && i <= parent.getChildCount() - 1; i += step) {
View child = parent.getChildAt(i);
if (!itemIsObscuredByHeader(parent, child, firstHeader, LinearLayout.VERTICAL)) {
return child;
}
}
return null;
}
/**
* Determines if an item is obscured by a header
*
*
* @param parent
* @param item to determine if obscured by header
* @param header that might be obscuring the item
* @param orientation of the {@link RecyclerView}
* @return true if the item view is obscured by the header view
*/
private boolean itemIsObscuredByHeader(RecyclerView parent, View item, View header, int orientation) {
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) item.getLayoutParams();
dimensionCalculator.initMargins(tempRect1, header);
int adapterPosition = parent.getChildAdapterPosition(item);
if (adapterPosition == RecyclerView.NO_POSITION || headerProvider.getHeader(parent, adapterPosition) != header) {
// Resolves https://github.com/timehop/sticky-headers-recyclerview/issues/36
// Handles an edge case where a trailing header is smaller than the current sticky header.
return false;
}
if (orientation == LinearLayoutManager.VERTICAL) {
int itemTop = item.getTop() - layoutParams.topMargin;
int headerBottom = getListTop(parent) + header.getBottom() + tempRect1.bottom + tempRect1.top;
if (itemTop >= headerBottom) {
return false;
}
} else {
int itemLeft = item.getLeft() - layoutParams.leftMargin;
int headerRight = getListLeft(parent) + header.getRight() + tempRect1.right + tempRect1.left;
if (itemLeft >= headerRight) {
return false;
}
}
return true;
}
private int getListTop(RecyclerView view) {
if (view.getLayoutManager().getClipToPadding()) {
return view.getPaddingTop();
} else {
return 0;
}
}
private int getListLeft(RecyclerView view) {
if (view.getLayoutManager().getClipToPadding()) {
return view.getPaddingLeft();
} else {
return 0;
}
}
}

View File

@ -0,0 +1,24 @@
package hikapro.com.backpack.presenter.adapters.helper.items;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Created by tariel on 30/04/16.
*/
public interface HeaderProvider {
/**
* Will provide a header view for a given position in the RecyclerView
*
* @param recyclerView that will display the header
* @param position that will be headed by the header
* @return a header view for the given position and list
*/
View getHeader(RecyclerView recyclerView, int position);
/**
* TODO: describe this functionality and its necessity
*/
void invalidate();
}

View File

@ -0,0 +1,70 @@
package hikapro.com.backpack.presenter.adapters.helper.items;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Created by tariel on 30/04/16.
*/
public class HeaderRenderer {
private final DimensionCalculator dimensionCalculator;
/**
* The following field is used as a buffer for internal calculations. Its sole purpose is to avoid
* allocating new Rect every time we need one.
*/
private final Rect mTempRect = new Rect();
public HeaderRenderer() {
this.dimensionCalculator = new DimensionCalculator();
}
/**
* Draws a header to a canvas, offsetting by some x and y amount
*
* @param recyclerView the parent recycler view for drawing the header into
* @param canvas the canvas on which to draw the header
* @param header the view to draw as the header
* @param offset a Rect used to define the x/y offset of the header. Specify x/y offset by setting
* the {@link Rect#left} and {@link Rect#top} properties, respectively.
*/
public void drawHeader(RecyclerView recyclerView, Canvas canvas, View header, Rect offset) {
canvas.save();
if (recyclerView.getLayoutManager().getClipToPadding()) {
// Clip drawing of headers to the padding of the RecyclerView. Avoids drawing in the padding
initClipRectForHeader(mTempRect, recyclerView, header);
canvas.clipRect(mTempRect);
}
canvas.translate(offset.left, offset.top);
header.draw(canvas);
canvas.restore();
}
/**
* Initializes a clipping rect for the header based on the margins of the header and the padding of the
* recycler.
* FIXME: Currently right margin in VERTICAL orientation and bottom margin in HORIZONTAL
* orientation are clipped so they look accurate, but the headers are not being drawn at the
* correctly smaller width and height respectively.
*
* @param clipRect {@link Rect} for clipping a provided header to the padding of a recycler view
* @param recyclerView for which to provide a header
* @param header for clipping
*/
private void initClipRectForHeader(Rect clipRect, RecyclerView recyclerView, View header) {
dimensionCalculator.initMargins(clipRect, header);
clipRect.set(
recyclerView.getPaddingLeft(),
recyclerView.getPaddingTop(),
recyclerView.getWidth() - recyclerView.getPaddingRight() - clipRect.right,
recyclerView.getHeight() - recyclerView.getPaddingBottom());
}
}

View File

@ -0,0 +1,56 @@
package hikapro.com.backpack.presenter.adapters.helper.items;
import android.support.v4.util.LongSparseArray;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by tariel on 30/04/16.
*/
public class HeaderViewCache implements HeaderProvider {
private final StickyHeaderAdapter adapter;
private final LongSparseArray<View> headerViews = new LongSparseArray<>();
public HeaderViewCache(StickyHeaderAdapter adapter) {
this.adapter = adapter;
}
@Override
public View getHeader(RecyclerView parent, int position) {
long headerId = adapter.getHeaderId(position);
View header = headerViews.get(headerId);
if (header == null) {
//TODO - recycle views
RecyclerView.ViewHolder viewHolder = adapter.onCreateHeaderViewHolder(parent);
adapter.onBindHeaderViewHolder(viewHolder, position);
header = viewHolder.itemView;
if (header.getLayoutParams() == null) {
header.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
int widthSpec;
int heightSpec;
widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,
parent.getPaddingLeft() + parent.getPaddingRight(), header.getLayoutParams().width);
int childHeight = ViewGroup.getChildMeasureSpec(heightSpec,
parent.getPaddingTop() + parent.getPaddingBottom(), header.getLayoutParams().height);
header.measure(childWidth, childHeight);
header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight());
headerViews.put(headerId, header);
}
return header;
}
@Override
public void invalidate() {
headerViews.clear();
}
}

View File

@ -0,0 +1,104 @@
package hikapro.com.backpack.presenter.adapters.helper.items;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.View;
import hikapro.com.backpack.R;
import hikapro.com.backpack.presenter.adapters.ItemListAdapter;
/**
* Created by tariel on 02/05/16.
*/
public class ItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
// we want to cache these and not allocate anything repeatedly in the onChildDraw method
Drawable background;
Drawable xMark;
int xMarkMargin;
boolean initiated;
ItemListAdapter adapter;
Context context;
public ItemSwipeCallback(int dragDirs, int swipeDirs, ItemListAdapter adapter, Context context) {
super(dragDirs, swipeDirs);
this.adapter = adapter;
this.context = context;
}
private void init() {
background = new ColorDrawable(Color.RED);
xMark = ContextCompat.getDrawable(context, R.drawable.ic_clear_24dp);
xMark.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
xMarkMargin = (int) context.getResources().getDimension(R.dimen.ic_clear_margin);
initiated = true;
}
// not important, we don't want drag & drop
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
@Override
public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int position = viewHolder.getAdapterPosition();
if (adapter.isUndoOn() && adapter.isPendingRemoval(position)) {
return 0;
}
return super.getSwipeDirs(recyclerView, viewHolder);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
int swipedPosition = viewHolder.getAdapterPosition();
boolean undoOn = adapter.isUndoOn();
if (undoOn) {
adapter.pendingRemoval(swipedPosition);
} else {
adapter.remove(swipedPosition);
}
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
View itemView = viewHolder.itemView;
// not sure why, but this method get's called for viewholder that are already swiped away
if (viewHolder.getAdapterPosition() == -1) {
// not interested in those
return;
}
if (!initiated) {
init();
}
// draw red background
background.setBounds(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom());
background.draw(c);
// draw x mark
int itemHeight = itemView.getBottom() - itemView.getTop();
int intrinsicWidth = xMark.getIntrinsicWidth();
int intrinsicHeight = xMark.getIntrinsicWidth();
int xMarkLeft = itemView.getRight() - xMarkMargin - intrinsicWidth;
int xMarkRight = itemView.getRight() - xMarkMargin;
int xMarkTop = itemView.getTop() + (itemHeight - intrinsicHeight)/2;
int xMarkBottom = xMarkTop + intrinsicHeight;
xMark.setBounds(xMarkLeft, xMarkTop, xMarkRight, xMarkBottom);
xMark.draw(c);
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}

View File

@ -0,0 +1,88 @@
package hikapro.com.backpack.presenter.adapters.helper.items;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Created by tariel on 02/05/16.
*/
public class ItemSwipeDecoration extends RecyclerView.ItemDecoration {
// we want to cache this and not allocate anything repeatedly in the onDraw method
Drawable background;
boolean initiated;
private void init() {
background = new ColorDrawable(Color.RED);
initiated = true;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (!initiated) {
init();
}
// only if animation is in progress
if (parent.getItemAnimator().isRunning()) {
// some items might be animating down and some items might be animating up to close the gap left by the removed item
// this is not exclusive, both movement can be happening at the same time
// to reproduce this leave just enough items so the first one and the last one would be just a little off screen
// then remove one from the middle
// find first child with translationY > 0
// and last one with translationY < 0
// we're after a rect that is not covered in recycler-view views at this point in time
View lastViewComingDown = null;
View firstViewComingUp = null;
// this is fixed
int left = 0;
int right = parent.getWidth();
// this we need to find out
int top = 0;
int bottom = 0;
// find relevant translating views
int childCount = parent.getLayoutManager().getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getLayoutManager().getChildAt(i);
if (child.getTranslationY() < 0) {
// view is coming down
lastViewComingDown = child;
} else if (child.getTranslationY() > 0) {
// view is coming up
if (firstViewComingUp == null) {
firstViewComingUp = child;
}
}
}
if (lastViewComingDown != null && firstViewComingUp != null) {
// views are coming down AND going up to fill the void
top = lastViewComingDown.getBottom() + (int) lastViewComingDown.getTranslationY();
bottom = firstViewComingUp.getTop() + (int) firstViewComingUp.getTranslationY();
} else if (lastViewComingDown != null) {
// views are going down to fill the void
top = lastViewComingDown.getBottom() + (int) lastViewComingDown.getTranslationY();
bottom = lastViewComingDown.getBottom();
} else if (firstViewComingUp != null) {
// views are coming up to fill the void
top = firstViewComingUp.getTop();
bottom = firstViewComingUp.getTop() + (int) firstViewComingUp.getTranslationY();
}
background.setBounds(left, top, right, bottom);
background.draw(c);
}
super.onDraw(c, parent, state);
}
}

View File

@ -0,0 +1,19 @@
package hikapro.com.backpack.presenter.adapters.helper.items;
/**
* Created by tariel on 30/04/16.
*/
public interface ItemVisibilityAdapter {
/**
*
* Return true the specified adapter position is visible, false otherwise
*
* The implementation of this method will typically return true if
* the position is between the layout manager's findFirstVisibleItemPosition
* and findLastVisibleItemPosition (inclusive).
*
* @param position the adapter position
*/
boolean isPositionVisible(final int position);
}

View File

@ -0,0 +1,15 @@
package hikapro.com.backpack.presenter.adapters.helper.items;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
/**
* Created by tariel on 01/05/16.
*/
public interface StickyHeaderAdapter<VH extends RecyclerView.ViewHolder> {
long getHeaderId(int position);
VH onCreateHeaderViewHolder(ViewGroup parent);
void onBindHeaderViewHolder(VH holder, int position);
int getItemCount();
}

View File

@ -0,0 +1,158 @@
package hikapro.com.backpack.presenter.adapters.helper.items;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;
import android.widget.LinearLayout;
/**
* Created by tariel on 01/05/16.
*/
public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
private final StickyHeaderAdapter adapter;
private final ItemVisibilityAdapter visibilityAdapter;
private final SparseArray<Rect> headerRects = new SparseArray<>();
private final HeaderProvider headerProvider;
private final HeaderPositionCalculator headerPositionCalculator;
private final HeaderRenderer renderer;
private final DimensionCalculator dimensionCalculator;
private final Rect rect = new Rect();
public StickyHeaderDecoration(StickyHeaderAdapter adapter) {
this(adapter, new DimensionCalculator(), null);
}
private StickyHeaderDecoration(StickyHeaderAdapter adapter,
DimensionCalculator dimensionCalculator, ItemVisibilityAdapter visibilityAdapter) {
this(adapter, dimensionCalculator, new HeaderRenderer(), new HeaderViewCache(adapter), visibilityAdapter);
}
private StickyHeaderDecoration(StickyHeaderAdapter adapter, DimensionCalculator dimensionCalculator,
HeaderRenderer headerRenderer, HeaderProvider headerProvider,
ItemVisibilityAdapter visibilityAdapter) {
this(adapter, headerRenderer, dimensionCalculator, headerProvider,
new HeaderPositionCalculator(adapter, headerProvider,
dimensionCalculator), visibilityAdapter);
}
private StickyHeaderDecoration(StickyHeaderAdapter adapter, HeaderRenderer headerRenderer,
DimensionCalculator dimensionCalculator, HeaderProvider headerProvider,
HeaderPositionCalculator headerPositionCalculator, ItemVisibilityAdapter visibilityAdapter) {
this.adapter = adapter;
this.headerProvider = headerProvider;
this.renderer = headerRenderer;
this.dimensionCalculator = dimensionCalculator;
this.headerPositionCalculator = headerPositionCalculator;
this.visibilityAdapter = visibilityAdapter;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int itemPosition = parent.getChildAdapterPosition(view);
if (itemPosition == RecyclerView.NO_POSITION) {
return;
}
if (headerPositionCalculator.hasNewHeader(itemPosition)) {
View header = getHeaderView(parent, itemPosition);
setItemOffsetsForHeader(outRect, header, LinearLayout.VERTICAL);
}
}
/**
* Sets the offsets for the first item in a section to make room for the header view
*
* @param itemOffsets rectangle to define offsets for the item
* @param header view used to calculate offset for the item
* @param orientation used to calculate offset for the item
*/
private void setItemOffsetsForHeader(Rect itemOffsets, View header, int orientation) {
dimensionCalculator.initMargins(rect, header);
if (orientation == LinearLayoutManager.VERTICAL) {
itemOffsets.top = header.getHeight() + rect.top + rect.bottom;
} else {
itemOffsets.left = header.getWidth() + rect.left +
rect.right;
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
final int childCount = parent.getChildCount();
if (childCount <= 0 || adapter.getItemCount() <= 0) {
return;
}
for (int i = 0; i < childCount; i++) {
View itemView = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(itemView);
if (position == RecyclerView.NO_POSITION) {
continue;
}
boolean hasStickyHeader = headerPositionCalculator.hasStickyHeader(itemView, LinearLayout.VERTICAL, position);
if (hasStickyHeader || headerPositionCalculator.hasNewHeader(position)) {
View header = headerProvider.getHeader(parent, position);
//re-use existing Rect, if any.
Rect headerOffset = headerRects.get(position);
if (headerOffset == null) {
headerOffset = new Rect();
headerRects.put(position, headerOffset);
}
headerPositionCalculator.initHeaderBounds(headerOffset, parent, header, itemView, hasStickyHeader);
renderer.drawHeader(parent, c, header, headerOffset);
}
}
}
/**
* Gets the position of the header under the specified (x, y) coordinates.
*
* @param x x-coordinate
* @param y y-coordinate
* @return position of header, or -1 if not found
*/
public int findHeaderPositionUnder(int x, int y) {
for (int i = 0; i < headerRects.size(); i++) {
Rect rect = headerRects.get(headerRects.keyAt(i));
if (rect.contains(x, y)) {
int position = headerRects.keyAt(i);
if (visibilityAdapter == null || visibilityAdapter.isPositionVisible(position)) {
return position;
}
}
}
return -1;
}
/**
* Gets the header view for the associated position. If it doesn't exist yet, it will be
* created, measured, and laid out.
*
* @param parent the recyclerview
* @param position the position to get the header view for
* @return Header view
*/
public View getHeaderView(RecyclerView parent, int position) {
return headerProvider.getHeader(parent, position);
}
/**
* Invalidates cached headers. This does not invalidate the recyclerview, you should do that manually after
* calling this method.
*/
public void invalidateHeaders() {
headerProvider.invalidate();
headerRects.clear();
}
}

View File

@ -1,4 +1,4 @@
package hikapro.com.backpack.presenter.adapters.helper;
package hikapro.com.backpack.presenter.adapters.helper.sets;
/**
* Created by N551 on 25.04.2016.

View File

@ -1,4 +1,4 @@
package hikapro.com.backpack.presenter.adapters.helper;
package hikapro.com.backpack.presenter.adapters.helper.sets;
/**
* Created by N551 on 25.04.2016.

View File

@ -1,4 +1,4 @@
package hikapro.com.backpack.presenter.adapters.helper;
package hikapro.com.backpack.presenter.adapters.helper.sets;
import android.support.v7.widget.RecyclerView;

View File

@ -1,4 +1,4 @@
package hikapro.com.backpack.presenter.adapters.helper;
package hikapro.com.backpack.presenter.adapters.helper.sets;
import android.graphics.Canvas;
import android.support.v7.widget.RecyclerView;

View File

@ -1,12 +1,11 @@
package hikapro.com.backpack.view;
import android.content.Context;
import android.widget.Toast;
import hikapro.com.backpack.model.entities.Item;
import hikapro.com.backpack.model.entities.Set;
import hikapro.com.backpack.presenter.Presenter;
import hikapro.com.backpack.presenter.adapters.helper.OnStartDragListener;
import hikapro.com.backpack.presenter.adapters.helper.sets.OnStartDragListener;
/**
* Created by tariel on 19/04/16.

View File

@ -7,8 +7,12 @@ import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SearchView;
import android.widget.Toast;
import hikapro.com.backpack.R;
@ -18,7 +22,8 @@ import hikapro.com.backpack.presenter.ItemListPresenter;
import hikapro.com.backpack.presenter.Presenter;
public class ItemListFragment extends Fragment implements hikapro.com.backpack.view.View.ItemList {
public class ItemListFragment extends Fragment implements hikapro.com.backpack.view.View.ItemList,
SearchView.OnQueryTextListener {
private static final String BUNDLE_SET_KEY = "BUNDLE_SET_KEY";
private hikapro.com.backpack.view.View.ActivityCallback activityCallback;
@ -40,6 +45,34 @@ public class ItemListFragment extends Fragment implements hikapro.com.backpack.v
return ret;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_main, menu);
final MenuItem item = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView) item.getActionView();
searchView.setOnQueryTextListener(this);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
presenter.filter(newText);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
// life cycle -->
@Override
public void onAttach(Context context) {
@ -65,6 +98,7 @@ public class ItemListFragment extends Fragment implements hikapro.com.backpack.v
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
Log.i(this.toString(), "onCreate");
}
@Override

View File

@ -6,7 +6,6 @@ import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@ -14,7 +13,7 @@ import android.view.ViewGroup;
import hikapro.com.backpack.model.entities.Set;
import hikapro.com.backpack.presenter.Presenter;
import hikapro.com.backpack.presenter.adapters.helper.OnStartDragListener;
import hikapro.com.backpack.presenter.adapters.helper.sets.OnStartDragListener;
public class SetListFragment extends Fragment implements hikapro.com.backpack.view.View.SetList, OnStartDragListener{

View File

@ -1,31 +0,0 @@
package hikapro.com.backpack.view.recycler;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import hikapro.com.backpack.R;
/**
* Created by tariel on 20/04/16.
*/
public class CategoryViewHolder extends RecyclerView.ViewHolder {
public LinearLayout section;
public TextView sectionText;
public RecyclerView itemsRecycler;
public CategoryViewHolder(View v) {
super(v);
setupViews(v);
}
private void setupViews(View view) {
section = (LinearLayout)view.findViewById(R.id.linear);
sectionText = (TextView)view.findViewById(R.id.section_text);
itemsRecycler = (RecyclerView) view.findViewById(R.id.category_inner_recycler);
}
}

View File

@ -0,0 +1,25 @@
package hikapro.com.backpack.view.recycler;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;
import hikapro.com.backpack.R;
/**
* Created by tariel on 01/05/16.
*/
public class HeaderViewHolder extends RecyclerView.ViewHolder {
public int id;
public TextView title;
public HeaderViewHolder(View v) {
super(v);
setupViews(v);
}
private void setupViews(View view) {
title = (TextView) view.findViewById(R.id.header);
}
}

View File

@ -2,18 +2,21 @@ package hikapro.com.backpack.view.recycler;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import hikapro.com.backpack.R;
/**
* Created by tariel on 20/04/16.
* Created by tariel on 01/05/16.
*/
public class ItemViewHolder extends RecyclerView.ViewHolder {
public TextView title;
public int id;
public int categoryId;
public TextView title;
public Button undoButton;
public ItemViewHolder(View v) {
super(v);
@ -21,6 +24,7 @@ public class ItemViewHolder extends RecyclerView.ViewHolder {
}
private void setupViews(View view) {
title = (TextView) view.findViewById(R.id.item_text);
title = (TextView) view.findViewById(R.id.item_txt);
undoButton = (Button) view.findViewById(R.id.undo_button);
}
}

View File

@ -8,7 +8,7 @@ import android.view.View;
import android.widget.TextView;
import hikapro.com.backpack.R;
import hikapro.com.backpack.presenter.adapters.helper.ItemTouchHelperViewHolder;
import hikapro.com.backpack.presenter.adapters.helper.sets.ItemTouchHelperViewHolder;
/**
* Created by tariel on 20/04/16.

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@ -6,7 +6,7 @@
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/categories_main_recycler"
android:id="@+id/items_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:background="@android:color/transparent"
android:textSize="16sp"
android:id="@+id/item_text"
android:textStyle="bold"
android:height="30dp"
/>

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:singleLine="true"
android:textAllCaps="true"
android:background="@android:color/transparent"
android:textSize="16sp"
android:id="@+id/section_text"
android:textStyle="bold"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/linear">
<android.support.v7.widget.RecyclerView
android:id="@+id/category_inner_recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sticky_header_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="#001F3F"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="@android:color/white"
tools:text="Animals starting with A"
android:id="@+id/header"
tools:context=".MainActivity"/>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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="56dp"
>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:clickable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:textSize="14sp"
tools:text="Aardvark"
android:id="@+id/item_txt"
tools:context=".MainActivity"/>
<Button
android:id="@+id/undo_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Undo"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:layout_gravity="end|center_vertical"
style="@style/Base.Widget.AppCompat.Button.Borderless"/>
</FrameLayout>

View File

@ -0,0 +1,20 @@
<menu 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"
tools:context=".MainActivity">
<item
android:id="@+id/action_settings"
android:orderInCategory="1"
android:title="Settings"
android:showAsAction="never" />
app:showAsAction="never" />
<item
android:id="@+id/action_search"
android:icon="@android:drawable/ic_menu_search"
android:showAsAction="always|collapseActionView"
app:showAsAction="always|collapseActionView"
android:actionViewClass="android.widget.SearchView"
android:visible="true"
android:title="Search"/>
</menu>

View File

@ -2,4 +2,5 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="ic_clear_margin">16dp</dimen>
</resources>