Using RecyclerView rather than ListView

This commit is contained in:
zeapo 2014-10-19 16:45:54 +02:00
parent 83c3231ab0
commit 24892e6a95
13 changed files with 178 additions and 452 deletions

View file

@ -30,6 +30,7 @@ repositories {
dependencies {
compile "com.android.support:appcompat-v7:21.+"
compile "com.android.support:recyclerview-v7:21.+"
//compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':libraries:openpgp-api-lib')

View file

@ -1,36 +1,22 @@
package com.zeapo.pwdstore;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import com.fortysevendeg.swipelistview.BaseSwipeListViewListener;
import com.fortysevendeg.swipelistview.SwipeListView;
import com.fortysevendeg.swipelistview.SwipeListViewListener;
import com.zeapo.pwdstore.utils.PasswordAdapter;
import com.zeapo.pwdstore.utils.PasswordItem;
import com.zeapo.pwdstore.utils.PasswordRecyclerAdapter;
import com.zeapo.pwdstore.utils.PasswordRepository;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
@ -40,24 +26,20 @@ import java.util.Stack;
* with a GridView.
* <p />
*/
public class PasswordFragment extends Fragment implements SwipeListViewListener{
public class PasswordFragment extends Fragment{
public interface OnFragmentInteractionListener {
public void onFragmentInteraction(PasswordItem item);
}
// store the pass files list in a stack
private Stack<ArrayList<PasswordItem>> passListStack;
private Stack<Integer> scrollPosition;
private PasswordRecyclerAdapter recyclerAdapter;
private RecyclerView recyclerView;
private RecyclerView.LayoutManager mLayoutManager;
private OnFragmentInteractionListener mListener;
/**
* The fragment's ListView/GridView.
*/
private SwipeListView mListView;
/**
* The Adapter which will be used to populate the ListView/GridView with
* Views.
*/
private PasswordAdapter mAdapter;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
@ -71,19 +53,23 @@ public class PasswordFragment extends Fragment implements SwipeListViewListener{
String path = getArguments().getString("Path");
passListStack = new Stack<ArrayList<PasswordItem>>();
mAdapter = new PasswordAdapter((PasswordStore) getActivity(), PasswordRepository.getPasswords(new File(path)));
scrollPosition = new Stack<Integer>();
recyclerAdapter = new PasswordRecyclerAdapter((PasswordStore) getActivity(), mListener, PasswordRepository.getPasswords(new File(path)));
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_password, container, false);
View view = inflater.inflate(R.layout.password_recycler_view, container, false);
// Set the adapter
mListView = (SwipeListView) view.findViewById(R.id.pass_list);
((AdapterView<ListAdapter>) mListView).setAdapter(mAdapter);
mListView.setSwipeListViewListener(this);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(getActivity());
recyclerView = (RecyclerView) view.findViewById(R.id.pass_recycler);
recyclerView.setLayoutManager(mLayoutManager);
//
// // Set the adapter
recyclerView.setAdapter(recyclerAdapter);
return view;
}
@ -92,18 +78,20 @@ public class PasswordFragment extends Fragment implements SwipeListViewListener{
super.onAttach(activity);
try {
mListener = new OnFragmentInteractionListener() {
@Override
public void onFragmentInteraction(PasswordItem item) {
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
passListStack.push((ArrayList<PasswordItem>) mAdapter.getValues().clone());
mAdapter.clear();
mAdapter.addAll(PasswordRepository.getPasswords(item.getFile()));
passListStack.push((ArrayList<PasswordItem>) recyclerAdapter.getValues().clone());
scrollPosition.push(recyclerView.getVerticalScrollbarPosition());
Log.d("FRAG", scrollPosition.peek() + "");
recyclerView.scrollToPosition(0);
recyclerAdapter.clear();
recyclerAdapter.addAll(PasswordRepository.getPasswords(item.getFile()));
((ActionBarActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
@Override
public void savePosition(Integer position) {
}
@ -114,114 +102,22 @@ public class PasswordFragment extends Fragment implements SwipeListViewListener{
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
@Override
public void onPause() {
super.onPause();
mListener.savePosition(mListView.getFirstVisiblePosition());
mListView.closeOpenedItems();
}
@Override
public void onOpened(int i, boolean b) {
}
@Override
public void onClosed(int i, boolean b) {
}
@Override
public void onListChanged() {
}
@Override
public void onMove(int i, float v) {
}
@Override
public void onStartOpen(int i, int i2, boolean b) {
}
@Override
public void onStartClose(int i, boolean b) {
}
@Override
public void onClickFrontView(int i) {
if (mAdapter.getItem(i).getType() == PasswordItem.TYPE_PASSWORD) {
mListView.openAnimate(i);
} else if (null != mListener) {
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mListener.onFragmentInteraction(mAdapter.getItem(i));
}
}
@Override
public void onClickBackView(int i) {
mListView.closeAnimate(i);
}
@Override
public void onDismiss(int[] ints) {
}
@Override
public int onChangeSwipeMode(int i) {
return 0;
}
@Override
public void onChoiceChanged(int i, boolean b) {
}
@Override
public void onChoiceStarted() {
}
@Override
public void onChoiceEnded() {
}
@Override
public void onFirstListItem() {
}
@Override
public void onLastListItem() {
}
public interface OnFragmentInteractionListener {
public void onFragmentInteraction(PasswordItem item);
public void savePosition(Integer position);
// mListener.savePosition(mListView.getFirstVisiblePosition());
// mListView.closeOpenedItems();
}
public void updateAdapter() {
mAdapter.clear();
mAdapter.addAll(PasswordRepository.getPasswords(new File(getArguments().getString("Path"))));
mListView.setAdapter((ListAdapter) mAdapter);
recyclerAdapter.clear();
recyclerAdapter.addAll(PasswordRepository.getPasswords(new File(getArguments().getString("Path"))));
}
public void popBack() {
mAdapter.clear();
mAdapter.addAll(passListStack.pop());
recyclerView.scrollToPosition(scrollPosition.pop());
recyclerAdapter.clear();
recyclerAdapter.addAll(passListStack.pop());
}
public boolean isNotEmpty() {

View file

@ -30,7 +30,7 @@ import java.util.ArrayList;
import java.util.Stack;
public class PasswordStore extends ActionBarActivity implements ToCloneOrNot.OnFragmentInteractionListener {
public class PasswordStore extends ActionBarActivity {
private Stack<Integer> scrollPositions;
/** if we leave the activity to do something, do not add any other fragment */
public boolean leftActivity = false;
@ -231,11 +231,6 @@ public class PasswordStore extends ActionBarActivity implements ToCloneOrNot.OnF
}
}
@Override
public void onFragmentInteraction(Uri uri) {
}
private void checkLocalRepository() {
checkLocalRepository(PasswordRepository.getWorkTree());
}

View file

@ -9,45 +9,8 @@ import android.view.View;
import android.view.ViewGroup;
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link ToCloneOrNot.OnFragmentInteractionListener} interface
* to handle interaction events.
* Use the {@link ToCloneOrNot#newInstance} factory method to
* create an instance of this fragment.
*
*/
public class ToCloneOrNot extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
private OnFragmentInteractionListener mListener;
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment ToCloneOrNot.
*/
// TODO: Rename and change types and number of parameters
public static ToCloneOrNot newInstance(String param1, String param2) {
ToCloneOrNot fragment = new ToCloneOrNot();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
public ToCloneOrNot() {
// Required empty public constructor
}
@ -55,10 +18,6 @@ public class ToCloneOrNot extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
@ -68,43 +27,4 @@ public class ToCloneOrNot extends Fragment {
return inflater.inflate(R.layout.fragment_to_clone_or_not, container, false);
}
// TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnFragmentInteractionListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
public void onFragmentInteraction(Uri uri);
}
}

View file

@ -1,104 +0,0 @@
package com.zeapo.pwdstore.utils;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.DataSetObserver;
import android.graphics.Typeface;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ExpandableListAdapter;
import android.widget.GridLayout;
import android.widget.ImageButton;
import android.widget.TextView;
import com.zeapo.pwdstore.PasswordStore;
import com.zeapo.pwdstore.R;
import com.zeapo.pwdstore.crypto.PgpHandler;
import org.apache.commons.io.FileUtils;
import java.util.ArrayList;
public class PasswordAdapter extends ArrayAdapter<PasswordItem>{
private final PasswordStore activity;
private final ArrayList<PasswordItem> values;
static class ViewHolder {
public TextView name;
public TextView type;
public TextView back_name;
}
public PasswordAdapter(PasswordStore activity, ArrayList<PasswordItem> values) {
super(activity, R.layout.password_row_layout, values);
this.values = values;
this.activity = activity;
}
public ArrayList<PasswordItem> getValues() {
return values;
}
@Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
View rowView = convertView;
final PasswordItem pass = values.get(i);
// reuse for performance, holder pattern!
if (rowView == null) {
LayoutInflater inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
rowView = inflater.inflate(R.layout.password_row_layout, viewGroup, false);
ViewHolder viewHolder = new ViewHolder();
viewHolder.name = (TextView) rowView.findViewById(R.id.label);
viewHolder.back_name = (TextView) rowView.findViewById(R.id.label_back);
viewHolder.type = (TextView) rowView.findViewById(R.id.type);
rowView.setTag(viewHolder);
}
ViewHolder holder = (ViewHolder) rowView.getTag();
holder.name.setText(pass.toString());
holder.back_name.setText(pass.toString());
if (pass.getType() == PasswordItem.TYPE_CATEGORY) {
holder.name.setTextColor(this.activity.getResources().getColor(android.R.color.holo_blue_dark));
holder.name.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
holder.type.setText("Category: ");
} else {
holder.type.setText("Password: ");
holder.name.setTextColor(this.activity.getResources().getColor(android.R.color.holo_orange_dark));
holder.name.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL));
holder.back_name.setTextColor(this.activity.getResources().getColor(android.R.color.white));
holder.back_name.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD_ITALIC));
View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.crypto_show_button:
activity.decryptPassword(pass);
break;
case R.id.crypto_delete_button:
activity.deletePassword(pass);
break;
}
}
};
((ImageButton) rowView.findViewById(R.id.crypto_show_button)).setOnClickListener(onClickListener);
((ImageButton) rowView.findViewById(R.id.crypto_delete_button)).setOnClickListener(onClickListener);
}
return rowView;
}
}

View file

@ -0,0 +1,97 @@
package com.zeapo.pwdstore.utils;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.zeapo.pwdstore.PasswordFragment;
import com.zeapo.pwdstore.PasswordStore;
import com.zeapo.pwdstore.R;
import java.util.ArrayList;
public class PasswordRecyclerAdapter extends RecyclerView.Adapter<PasswordRecyclerAdapter.ViewHolder> {
private final PasswordStore activity;
private final ArrayList<PasswordItem> values;
private final PasswordFragment.OnFragmentInteractionListener listener;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class ViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public View view;
public TextView name;
public TextView type;
public int position;
public ViewHolder(View v) {
super(v);
view = v;
name = (TextView) view.findViewById(R.id.label);
type = (TextView) view.findViewById(R.id.type);
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public PasswordRecyclerAdapter(PasswordStore activity, PasswordFragment.OnFragmentInteractionListener listener, ArrayList<PasswordItem> values) {
this.values = values;
this.activity = activity;
this.listener = listener;
}
// Create new views (invoked by the layout manager)
@Override
public PasswordRecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.password_row_layout, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final PasswordItem pass = values.get(position);
holder.name.setText(pass.getName());
holder.type.setText((pass.getType() == PasswordItem.TYPE_CATEGORY ? "Category" : "Password"));
holder.position = position;
holder.view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onFragmentInteraction(pass);
}
});
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return values.size();
}
public ArrayList<PasswordItem> getValues() {
return this.values;
}
public void clear() {
this.values.clear();
this.notifyDataSetChanged();
}
public void addAll(ArrayList<PasswordItem> list) {
this.values.addAll(list);
this.notifyDataSetChanged();
}
public void add(PasswordItem item) {
this.values.add(item);
this.notifyDataSetChanged();
}
}

View file

@ -2,10 +2,6 @@
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"
android:background="#eee"
tools:context=".pwdstore"
android:orientation="vertical">

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
tools:context="com.zeapo.pwdstore.PasswordFragment">
<GridView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="2" />
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center" />
</FrameLayout>

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
tools:context="com.zeapo.pwdstore.PasswordFragment">
<com.fortysevendeg.swipelistview.SwipeListView
xmlns:swipe="http://schemas.android.com/apk/res-auto"
android:id="@+id/pass_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:dividerHeight="@dimen/activity_vertical_margin"
android:divider="@android:color/transparent"
swipe:swipeFrontView="@+id/front"
swipe:swipeBackView="@+id/back"
swipe:swipeActionLeft="reveal"
swipe:swipeActionRight="reveal"
swipe:swipeMode="both"
swipe:swipeCloseAllItemsWhenMoveList="true"
swipe:swipeOpenOnLongPress="true"
swipe:swipeAnimationTime="200"
android:listSelector="#00000000"/>
</FrameLayout>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context="com.zeapo.pwdstore.PasswordFragment">
<android.support.v7.widget.RecyclerView
android:id="@+id/pass_recycler"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View file

@ -1,71 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/front"
android:tag="front"
android:background="@drawable/rectangle"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_gravity="start|center_vertical">
<GridLayout
android:id="@+id/back"
android:orientation="horizontal"
android:layout_width="match_parent"
<TextView
android:id="@+id/type"
android:text="TYPE"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="back"
android:background="@drawable/gray_rectangle"
android:layout_gravity="left|center_vertical">
<TextView
android:id="@+id/label_back"
android:text="FILE_NAME"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="8dp"
android:layout_column="0"/>
<ImageButton
android:id="@+id/crypto_delete_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ico_del"
android:background="@drawable/red_rectangle"
android:layout_gravity="center_vertical"
android:layout_column="2"/>
<ImageButton
android:id="@+id/crypto_show_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ico_eye"
android:background="@drawable/blue_rectangle"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
android:layout_column="3"/>
</GridLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:id="@+id/front"
android:tag="front"
android:background="@android:color/white"
android:layout_gravity="left|center_vertical">
<TextView
android:id="@+id/type"
android:text="TYPE"
android:layout_column="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:textStyle="bold"
android:layout_gravity="center_vertical"
/>
<TextView
android:id="@+id/label"
android:text="FILE_NAME"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="8dp"/>
</LinearLayout>
</FrameLayout>
android:padding="8dp"
android:textStyle="bold"
android:layout_gravity="center_vertical"
/>
<TextView
android:id="@+id/label"
android:text="FILE_NAME"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="8dp"/>
</LinearLayout>

View file

@ -9,6 +9,4 @@
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
-->
<item name="fragment_password" type="layout">@layout/fragment_password_grid</item>
</resources>

View file

@ -9,6 +9,4 @@
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
-->
<item name="fragment_password" type="layout">@layout/fragment_password_grid</item>
</resources>