Item animations working

This commit is contained in:
Phillip Hsu 2016-07-05 17:00:57 -07:00
parent 4eec10f86a
commit 9fb4727051
7 changed files with 134 additions and 55 deletions

View File

@ -0,0 +1,93 @@
package com.philliphsu.clock2;
import android.content.Context;
import android.os.AsyncTask;
import android.support.design.widget.Snackbar;
import android.view.View;
import com.philliphsu.clock2.alarms.AlarmsAdapter;
import com.philliphsu.clock2.model.DatabaseManager;
import com.philliphsu.clock2.util.AlarmUtils;
/**
* Created by Phillip Hsu on 7/1/2016.
*
* TODO: Generify this class for any item.
*/
public final class AsyncRecyclerViewItemChangeHandler {
private final Context mContext;
private final AlarmsAdapter mAdapter;
private final View mSnackbarAnchor;
/**
* @param snackbarAnchor an optional anchor for a Snackbar to anchor to
*/
public AsyncRecyclerViewItemChangeHandler(AlarmsAdapter adapter, View snackbarAnchor) {
mContext = snackbarAnchor.getContext();
mAdapter = adapter;
mSnackbarAnchor = snackbarAnchor;
}
public void asyncAddAlarm(final Alarm alarm) {
new AsyncTask<Void, Void, Long>() {
@Override
protected Long doInBackground(Void... params) {
return DatabaseManager.getInstance(mContext).insertAlarm(alarm);
}
@Override
protected void onPostExecute(Long aLong) {
// TODO: Snackbar/Toast here? If so, remove the code in AlarmUtils.scheduleAlarm() that does it.
mAdapter.addItem(alarm);
AlarmUtils.scheduleAlarm(mContext, alarm, true);
}
}.execute();
}
public void asyncUpdateAlarm(final Alarm oldAlarm, final Alarm newAlarm) {
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... params) {
mAdapter.updateItem(oldAlarm, newAlarm);
// Save the item to the db
return DatabaseManager.getInstance(mContext).updateAlarm(oldAlarm.id(), newAlarm);
}
@Override
protected void onPostExecute(Integer integer) {
// TODO: Snackbar/Toast here? If so, remove the code in AlarmUtils.scheduleAlarm() that does it.
}
}.execute();
}
public void asyncRemoveAlarm(final Alarm alarm) {
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... params) {
mAdapter.removeItem(alarm);
// Save the item to the db
return DatabaseManager.getInstance(mContext).deleteAlarm(alarm);
}
@Override
protected void onPostExecute(Integer integer) {
// TODO: Snackbar
if (mSnackbarAnchor != null) {
String message = mContext.getString(R.string.snackbar_item_deleted,
mContext.getString(R.string.alarm));
Snackbar.make(mSnackbarAnchor, message, Snackbar.LENGTH_LONG)
.setAction(R.string.snackbar_undo_item_deleted, new View.OnClickListener() {
@Override
public void onClick(View v) {
DatabaseManager.getInstance(mContext).insertAlarm(alarm);
if (alarm.isEnabled()) {
AlarmUtils.scheduleAlarm(mContext, alarm, true);
}
}
}).show();
}
}
}.execute();
}
}

View File

@ -1,40 +0,0 @@
package com.philliphsu.clock2;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Created by Phillip Hsu on 7/1/2016.
*/
public final class RecyclerViewItemChangeHandler<T> {
private final RecyclerView mRecyclerView;
private final View mSnackbarAnchor;
/**
* @param recyclerView the RecyclerView for which we should handle item change events
* @param snackbarAnchor an optional anchor for a Snackbar to anchor to
*/
public RecyclerViewItemChangeHandler(RecyclerView recyclerView, View snackbarAnchor) {
mRecyclerView = recyclerView;
mSnackbarAnchor = snackbarAnchor;
}
/**
* Dispatches an item change event to the item in the
* RecyclerView with the given stable id.
*/
// This won't work if the change on the item would cause it
// to be sorted in a different position!
public void notifyItemChanged(long id) {
if (id < 0) throw new IllegalArgumentException("id < 0");
int position = mRecyclerView.findViewHolderForItemId(id).getAdapterPosition();
mRecyclerView.getAdapter().notifyItemChanged(position);
}
public void notifyItemRemoved(long id) {
if (id < 0) throw new IllegalArgumentException("id < 0");
int position = mRecyclerView.findViewHolderForItemId(id).getAdapterPosition();
mRecyclerView.getAdapter().notifyItemRemoved(position);
}
}

View File

@ -13,6 +13,12 @@ public class AlarmsAdapter extends BaseAdapter<Alarm, AlarmViewHolder> {
public AlarmsAdapter(List<Alarm> alarms, OnListItemInteractionListener<Alarm> listener) {
super(Alarm.class, alarms, listener);
setHasStableIds(true);
}
@Override
public long getItemId(int position) {
return getItem(position).id();
}
@Override

View File

@ -22,7 +22,14 @@ public class AlarmsCursorAdapter extends RecyclerView.Adapter<AlarmViewHolder> {
public AlarmsCursorAdapter(OnListItemInteractionListener<Alarm> listener) {
mListener = listener;
setHasStableIds(true); // TODO: why do we need this?
// Excerpt from docs of notifyDataSetChanged():
// "RecyclerView will attempt to synthesize [artificially create?]
// visible structural change events [when items are inserted, removed or
// moved] for adapters that report that they have stable IDs when
// [notifyDataSetChanged()] is used. This can help for the purposes of
// animation and visual object persistence [?] but individual item views
// will still need to be rebound and relaid out."
setHasStableIds(true);
}
@Override

View File

@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
@ -16,6 +17,7 @@ import android.view.View;
import android.view.ViewGroup;
import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.AsyncRecyclerViewItemChangeHandler;
import com.philliphsu.clock2.OnListItemInteractionListener;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.editalarm.EditAlarmActivity;
@ -37,6 +39,7 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<List<Ala
public static final int REQUEST_CREATE_ALARM = 1;
private static final String TAG = "AlarmsFragment";
private AsyncRecyclerViewItemChangeHandler mAsyncRecyclerViewItemChangeHandler;
private AlarmsAdapter mAdapter;
@Bind(R.id.list) RecyclerView mList;
@ -79,6 +82,9 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<List<Ala
mList.setLayoutManager(new LinearLayoutManager(context));
mAdapter = new AlarmsAdapter(Collections.<Alarm>emptyList(), this);
mList.setAdapter(mAdapter);
mAsyncRecyclerViewItemChangeHandler = new AsyncRecyclerViewItemChangeHandler(
mAdapter, getActivity().findViewById(R.id.main_content));
return view;
}
@ -126,12 +132,19 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<List<Ala
switch (requestCode) {
case REQUEST_CREATE_ALARM:
// TODO: notifyItemInserted?
getLoaderManager().restartLoader(0, null, this);
Log.d(TAG, "Async add alarm");
final Alarm createdAlarm = data.getParcelableExtra(EditAlarmActivity.EXTRA_MODIFIED_ALARM);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mAsyncRecyclerViewItemChangeHandler.asyncAddAlarm(createdAlarm);
}
}, 1000);
break;
case REQUEST_EDIT_ALARM:
Alarm deletedAlarm;
if (data != null && (deletedAlarm = data.getParcelableExtra(
EditAlarmActivity.EXTRA_DELETED_ALARM)) != null) {
EditAlarmActivity.EXTRA_MODIFIED_ALARM)) != null) {
onListItemDeleted(deletedAlarm);
}
// TODO: notifyItemRemoved?
@ -153,6 +166,7 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<List<Ala
// TODO: This doesn't need to be defined in the interface.
// TODO: Rename to showDeletedSnackbar() or something
// TODO: This needs to prompt a reload of the list.
@Deprecated // TODO: Delete this method.
@Override
public void onListItemDeleted(final Alarm item) {
Snackbar.make(getActivity().findViewById(R.id.main_content),

View File

@ -58,7 +58,7 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
LoaderManager.LoaderCallbacks<Alarm> {
private static final String TAG = "EditAlarmActivity";
public static final String EXTRA_ALARM_ID = "com.philliphsu.clock2.editalarm.extra.ALARM_ID";
public static final String EXTRA_DELETED_ALARM = "com.philliphsu.clock2.editalarm.extra.DELETED_ALARM";
public static final String EXTRA_MODIFIED_ALARM = "com.philliphsu.clock2.editalarm.extra.MODIFIED_ALARM";
private static final RelativeSizeSpan AMPM_SIZE_SPAN = new RelativeSizeSpan(0.5f);
private static final int REQUEST_PICK_RINGTONE = 0;
@ -236,6 +236,7 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
alarm.setRecurring(i, isRecurringDay(i));
}
Intent intent = new Intent();
if (mOldAlarm != null) {
if (mOldAlarm.isEnabled()) {
Log.d(TAG, "Cancelling old alarm first");
@ -244,15 +245,15 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
// TODO: Do this in the background. AsyncTask?
mDatabaseManager.updateAlarm(mOldAlarm.id(), alarm);
} else {
// TODO: Do this in the background. AsyncTask?
mDatabaseManager.insertAlarm(alarm);
//mDatabaseManager.insertAlarm(alarm);
intent.putExtra(EXTRA_MODIFIED_ALARM, alarm);
}
if (alarm.isEnabled()) {
scheduleAlarm(alarm);
//scheduleAlarm(alarm);
}
setResult(RESULT_OK);
setResult(RESULT_OK, intent);
showEditorClosed();
}
@ -268,7 +269,7 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
}
mDatabaseManager.deleteAlarm(mOldAlarm);
Intent intent = new Intent();
intent.putExtra(EXTRA_DELETED_ALARM, mOldAlarm);
intent.putExtra(EXTRA_MODIFIED_ALARM, mOldAlarm);
setResult(RESULT_OK, intent);
}
showEditorClosed();

View File

@ -14,7 +14,7 @@ public class DatabaseManager {
private static DatabaseManager sDatabaseManager;
private final Context mContext;
private final AlarmDatabaseHelper mHelper;
private final AlarmDatabaseHelper mHelper; // TODO: Should we call close() when we're done?
private DatabaseManager(Context context) {
mContext = context.getApplicationContext();
@ -28,10 +28,8 @@ public class DatabaseManager {
return sDatabaseManager;
}
// TODO: why return an Alarm?
public Alarm insertAlarm(Alarm alarm) {
mHelper.insertAlarm(alarm);
return alarm;
public long insertAlarm(Alarm alarm) {
return mHelper.insertAlarm(alarm);
}
/**