ListView
(swipeable ListView
) is available in many apps like Gmail, Contact,...This design is helpful in quickly actions with each ListView
element (edit, delete, archive,...).By searching on Internet, we found that there are a lot of libraries use for making this design style. In this tutorial post, I will present AndroidSwipeLayout, it is powerful library by making swipe view, not only
ListView
like another. This is a project's DEMO VIDEO :Okey, let's start a new Android Project with min-sdk 15.
Building swipeable layouts
compile "com.daimajia.swipelayout:library:1.2.0@aar"
In each ListView
item layout, setting SwipeLayout
as root:
item_listview.xml
As you can see, <?xml version="1.0" encoding="utf-8"?>
<com.daimajia.swipe.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Bottom View Start-->
<LinearLayout
android:id="@+id/bottom_wrapper"
android:layout_width="100dp"
android:layout_height="match_parent"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#66ddff00">
<ImageView
android:id="@+id/edit_query"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@mipmap/edit" />
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#66FF3300">
<ImageView
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@mipmap/delete" />
</RelativeLayout>
</LinearLayout>
<!-- Bottom View End-->
<!-- Surface View Start -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff"
android:padding="10dp">
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:textAppearance="?android:attr/textAppearanceListItemSmall" />
</LinearLayout>
<!-- Surface View End -->
</com.daimajia.swipe.SwipeLayout>
SwipeLayout
has 2 children ViewGroups
, the first one is "Surface View (1)", which always visible and display view content, which be hidden and only visible when user swipe. The second one is "Bottom View (2)", which be hidden and only visible when user swipe.Creating a
ListView
header, also include SwipeLayout
as root, too. It will display total number of ListView
elements:
header_listview.xml
<?xml version="1.0" encoding="utf-8"?>
<com.daimajia.swipe.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/bottom_wrapper"
android:layout_width="80dp"
android:layout_height="match_parent"
android:background="#FF3300"
android:orientation="horizontal">
<TextView
android:id="@+id/total"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:textAppearance="?android:attr/textAppearanceListItemSmall" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#0000CC"
android:padding="10dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="@string/classmates"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:textStyle="bold" />
</LinearLayout>
</com.daimajia.swipe.SwipeLayout>
Creating running Activity
ListView
for our activity like this:
activity_main.xml
In programmatically code, with <Relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/list_item"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
SwipeLayout
, the first important option is "set show mode" for it. This line do this work:
swipeLayout.setShowMode(SwipeLayout.ShowMode.PullOut);
In this libary, showMode
can be PullOut
or LayDown
.Moreover, like the library docs say, we can set "listener" for swiping action like this:
swipeLayout.addSwipeListener(new SwipeLayout.SwipeListener() {
@Override
public void onClose(SwipeLayout layout) {
Log.i(TAG, "onClose");
}
@Override
public void onUpdate(SwipeLayout layout, int leftOffset, int topOffset) {
Log.i(TAG, "on swiping");
}
@Override
public void onStartOpen(SwipeLayout layout) {
Log.i(TAG, "on start open");
}
@Override
public void onOpen(SwipeLayout layout) {
Log.i(TAG, "the BottomView totally show");
}
@Override
public void onStartClose(SwipeLayout layout) {
Log.i(TAG, "the BottomView totally close");
}
@Override
public void onHandRelease(SwipeLayout layout, float xvel, float yvel) {
//when user's hand released.
}
});
Creating ListView adapter
ListView
, building an adapter based on ArrayAdapter
or BaseAdapter
. In this ListView
, I added 2 buttons (Edit and Delete) on each row. In addition to the familiar methods, we must handling their events (edit or delete selected item):
private View.OnClickListener onEditListener(final int position, final ViewHolder holder) {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
showEditDialog(position, holder);
}
};
}
/**
* Editting confirm dialog
* @param position
* @param holder
*/
private void showEditDialog(final int position, final ViewHolder holder) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity);
alertDialogBuilder.setTitle("EDIT ELEMENT");
final EditText input = new EditText(activity);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
input.setText(friends.get(position));
input.setLayoutParams(lp);
alertDialogBuilder.setView(input);
alertDialogBuilder
.setCancelable(false)
.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// get user input and set it to result edit text
friends.set(position, input.getText().toString().trim());
//notify data set changed
activity.updateAdapter();
holder.swipeLayout.close();
}
})
.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
// create alert dialog and show it
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
private View.OnClickListener onDeleteListener(final int position, final ViewHolder holder) {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
friends.remove(position);
holder.swipeLayout.close();
activity.updateAdapter();
}
};
}
By adding getView()
method and use a ViewHolder
class to get scrolling smoother, we have full code for this ListView
adapter:
ListViewAdapter.java
In line 92 and 114, when finish editing or deleting process, we will call back to main activity and update adapter and views by package info.devexchanges.swipeableview;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.daimajia.swipe.SwipeLayout;
import java.util.List;
public class ListViewAdapter extends ArrayAdapter<String> {
private MainActivity activity;
private List<String> friends;
public ListViewAdapter(MainActivity context, int resource, List<String> objects) {
super(context, resource, objects);
this.activity = context;
this.friends = objects;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
LayoutInflater inflater = (LayoutInflater) activity
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
// If holder not exist then locate all view from UI file.
if (convertView == null) {
// inflate UI from XML file
convertView = inflater.inflate(R.layout.item_listview, parent, false);
// get all UI view
holder = new ViewHolder(convertView);
// set tag for holder
convertView.setTag(holder);
} else {
// if holder created, get tag from view
holder = (ViewHolder) convertView.getTag();
}
holder.name.setText(getItem(position));
//handling buttons event
holder.btnEdit.setOnClickListener(onEditListener(position, holder));
holder.btnDelete.setOnClickListener(onDeleteListener(position, holder));
return convertView;
}
private View.OnClickListener onEditListener(final int position, final ViewHolder holder) {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
showEditDialog(position, holder);
}
};
}
/**
* Editting confirm dialog
* @param position
* @param holder
*/
private void showEditDialog(final int position, final ViewHolder holder) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity);
alertDialogBuilder.setTitle("EDIT ELEMENT");
final EditText input = new EditText(activity);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
input.setText(friends.get(position));
input.setLayoutParams(lp);
alertDialogBuilder.setView(input);
alertDialogBuilder
.setCancelable(false)
.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// get user input and set it to result edit text
friends.set(position, input.getText().toString().trim());
//notify data set changed
activity.updateAdapter();
holder.swipeLayout.close();
}
})
.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
// create alert dialog and show it
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
private View.OnClickListener onDeleteListener(final int position, final ViewHolder holder) {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
friends.remove(position);
holder.swipeLayout.close();
activity.updateAdapter();
}
};
}
private class ViewHolder {
private TextView name;
private View btnDelete;
private View btnEdit;
private SwipeLayout swipeLayout;
public ViewHolder(View v) {
swipeLayout = (SwipeLayout)v.findViewById(R.id.swipe_layout);
btnDelete = v.findViewById(R.id.delete);
btnEdit = v.findViewById(R.id.edit_query);
name = (TextView) v.findViewById(R.id.name);
swipeLayout.setShowMode(SwipeLayout.ShowMode.LayDown);
}
}
}
updateAdapter()
method. Put these line in activity code :
public void updateAdapter() {
adapter.notifyDataSetChanged(); //update adapter
totalClassmates.setText("(" + friendsList.size() + ")"); //update total friends in list
}
Final activity code
ArrayList
, set ListView
header (a SwipeLayout
, too), set adapter then, we have full code:
MainActivity.java
When app running, we have this output:package info.devexchanges.swipeableview;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.daimajia.swipe.SwipeLayout;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private ListView listView;
private ArrayAdapter<String> adapter;
private ArrayList<String> friendsList;
private TextView totalClassmates;
private SwipeLayout swipeLayout;
private final static String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView)findViewById(R.id.list_item);
friendsList = new ArrayList<>();
getDataFromFile();
setListViewHeader();
setListViewAdapter();
}
private void getDataFromFile() {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(getAssets().open("classmates.txt"), "UTF-8"));
// do reading, usually loop until end of file reading
String line = reader.readLine();
while (line != null && !line.equals("")) {
line = reader.readLine();
friendsList.add(line); // add line to array list
}
} catch (IOException e) {
//log the exception
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void setListViewHeader() {
LayoutInflater inflater = getLayoutInflater();
View header = inflater.inflate(R.layout.header_listview, listView, false);
totalClassmates = (TextView) header.findViewById(R.id.total);
swipeLayout = (SwipeLayout)header.findViewById(R.id.swipe_layout);
setSwipeViewFeatures();
listView.addHeaderView(header);
}
private void setSwipeViewFeatures() {
//set show mode.
swipeLayout.setShowMode(SwipeLayout.ShowMode.PullOut);
//add drag edge.(If the BottomView has 'layout_gravity' attribute, this line is unnecessary)
swipeLayout.addDrag(SwipeLayout.DragEdge.Left, findViewById(R.id.bottom_wrapper));
swipeLayout.addSwipeListener(new SwipeLayout.SwipeListener() {
@Override
public void onClose(SwipeLayout layout) {
Log.i(TAG, "onClose");
}
@Override
public void onUpdate(SwipeLayout layout, int leftOffset, int topOffset) {
Log.i(TAG, "on swiping");
}
@Override
public void onStartOpen(SwipeLayout layout) {
Log.i(TAG, "on start open");
}
@Override
public void onOpen(SwipeLayout layout) {
Log.i(TAG, "the BottomView totally show");
}
@Override
public void onStartClose(SwipeLayout layout) {
Log.i(TAG, "the BottomView totally close");
}
@Override
public void onHandRelease(SwipeLayout layout, float xvel, float yvel) {
//when user's hand released.
}
});
}
private void setListViewAdapter() {
adapter = new ListViewAdapter(this, R.layout.item_listview, friendsList);
listView.setAdapter(adapter);
totalClassmates.setText("(" + friendsList.size() + ")");
}
public void updateAdapter() {
adapter.notifyDataSetChanged(); //update adapter
totalClassmates.setText("(" + friendsList.size() + ")"); //update total friends in list
}
}
Conclusions
References:
- Libary page on Github: https://github.com/daimajia/AndroidSwipeLayout
- Library wiki and doc: https://github.com/daimajia/AndroidSwipeLayout/wiki