1
0
Fork 0
mirror of https://github.com/MatomoCamp/matomocamp-companion-android.git synced 2024-09-19 16:13:46 +02:00

Migrated most ListViews to RecyclerViews

- Use ConcatAdapter to replace header View in PersonInfoListFragment
- Use MultiChoiceHelper to implement MultiChoiceMode in RecyclerView
- Instances of TracksListFragment share a common RecycledViewPool to lower memory usage.
This commit is contained in:
Christophe Beyls 2016-12-01 16:09:18 +01:00
parent 71b030c994
commit 9fd6c6c374
24 changed files with 1721 additions and 245 deletions

View file

@ -34,6 +34,7 @@ ext {
dependencies {
compile "com.android.support:appcompat-v7:$supportLibraryVersion"
compile "com.android.support:recyclerview-v7:$supportLibraryVersion"
compile "com.android.support:cardview-v7:$supportLibraryVersion"
compile 'com.github.chrisbanes.photoview:library:1.2.4'
}

View file

@ -0,0 +1,202 @@
package android.support.v7.widget;
import android.support.annotation.NonNull;
import android.util.SparseArray;
import android.view.ViewGroup;
import java.util.Arrays;
import java.util.List;
/**
* Adapter which concatenates the items of multiple adapters.
* Doesn't support stable ids, but properly delegates changes notifications.
* <p>
* Adapters may provide multiple view types but they must not overlap.
* It's recommended to always use the item layout id as view type.
* <p>
* Warning: You need to use ConcatAdapter.getAdapterPosition(ViewHolder)
* in place of ViewHolder.getAdapterPosition() inside child adapters.
*
* @author Christophe Beyls
*/
public class ConcatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final RecyclerView.Adapter<RecyclerView.ViewHolder>[] adapters;
private final RecyclerView.AdapterDataObserver[] adapterObservers;
final int[] offsets;
private final SparseArray<RecyclerView.Adapter<RecyclerView.ViewHolder>> viewTypeAdapters = new SparseArray<>();
private class InternalObserver extends RecyclerView.AdapterDataObserver {
private final int adapterIndex;
InternalObserver(int adapterIndex) {
this.adapterIndex = adapterIndex;
}
@Override
public void onChanged() {
notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
notifyItemRangeChanged(positionStart + offsets[adapterIndex], itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
notifyItemRangeChanged(positionStart + offsets[adapterIndex], itemCount, payload);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
for (int i = adapterIndex + 1, size = offsets.length; i < size; ++i) {
offsets[i] += itemCount;
}
notifyItemRangeInserted(positionStart + offsets[adapterIndex], itemCount);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
for (int i = adapterIndex + 1, size = offsets.length; i < size; ++i) {
offsets[i] -= itemCount;
}
notifyItemRangeRemoved(positionStart + offsets[adapterIndex], itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
final int offset = offsets[adapterIndex];
notifyItemMoved(fromPosition + offset, toPosition + offset);
}
}
@SuppressWarnings("unchecked")
public ConcatAdapter(RecyclerView.Adapter<? extends RecyclerView.ViewHolder>... adapters) {
this.adapters = (RecyclerView.Adapter<RecyclerView.ViewHolder>[]) adapters;
final int size = adapters.length;
adapterObservers = new RecyclerView.AdapterDataObserver[size];
for (int i = 0; i < size; ++i) {
adapterObservers[i] = new InternalObserver(i);
}
offsets = new int[size];
}
/**
* @return The adapter position relative to the child adapter, if any.
*/
public static int getAdapterPosition(@NonNull RecyclerView.ViewHolder holder) {
int position = holder.getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
RecyclerView.Adapter adapter = holder.mOwnerRecyclerView.getAdapter();
if (adapter instanceof ConcatAdapter) {
final int[] offsets = ((ConcatAdapter) adapter).offsets;
final int index = Arrays.binarySearch(offsets, position);
if (index >= 0) {
position = 0;
} else {
position -= offsets[~index - 1];
}
}
}
return position;
}
private int getAdapterIndexForPosition(int position) {
int index = Arrays.binarySearch(offsets, position);
return (index < 0) ? ~index - 1 : index;
}
@Override
public int getItemViewType(int position) {
final int index = getAdapterIndexForPosition(position);
RecyclerView.Adapter<RecyclerView.ViewHolder> adapter = adapters[index];
int viewType = adapter.getItemViewType(position - offsets[index]);
if (viewTypeAdapters.get(viewType) == null) {
viewTypeAdapters.put(viewType, adapter);
}
return viewType;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return viewTypeAdapters.get(viewType).onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final int index = getAdapterIndexForPosition(position);
adapters[index].onBindViewHolder(holder, position - offsets[index]);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) {
final int index = getAdapterIndexForPosition(position);
adapters[index].onBindViewHolder(holder, position - offsets[index], payloads);
}
@Override
public int getItemCount() {
int count = 0;
for (int i = 0, size = adapters.length; i < size; ++i) {
offsets[i] = count;
count += adapters[i].getItemCount();
}
return count;
}
@Override
public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
if (!hasObservers()) {
for (int i = 0, size = adapters.length; i < size; ++i) {
adapters[i].registerAdapterDataObserver(adapterObservers[i]);
}
}
super.registerAdapterDataObserver(observer);
}
@Override
public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
super.unregisterAdapterDataObserver(observer);
if (!hasObservers()) {
for (int i = 0, size = adapters.length; i < size; ++i) {
adapters[i].unregisterAdapterDataObserver(adapterObservers[i]);
}
}
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
for (RecyclerView.Adapter<RecyclerView.ViewHolder> adapter : adapters) {
adapter.onAttachedToRecyclerView(recyclerView);
}
}
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
for (RecyclerView.Adapter<RecyclerView.ViewHolder> adapter : adapters) {
adapter.onDetachedFromRecyclerView(recyclerView);
}
}
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
viewTypeAdapters.get(holder.getItemViewType()).onViewAttachedToWindow(holder);
}
@Override
public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
viewTypeAdapters.get(holder.getItemViewType()).onViewDetachedFromWindow(holder);
}
@Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
viewTypeAdapters.get(holder.getItemViewType()).onViewRecycled(holder);
}
@Override
public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
return viewTypeAdapters.get(holder.getItemViewType()).onFailedToRecycleView(holder);
}
}

View file

@ -0,0 +1,160 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
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.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.view.View;
import android.widget.LinearLayout;
/**
* DividerItemDecoration is a {@link RecyclerView.ItemDecoration} that can be used as a divider
* between items of a {@link LinearLayoutManager}. It supports both {@link #HORIZONTAL} and
* {@link #VERTICAL} orientations.
* <p>
* <pre>
* mDividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
* mLayoutManager.getOrientation());
* recyclerView.addItemDecoration(mDividerItemDecoration);
* </pre>
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
public static final int VERTICAL = LinearLayout.VERTICAL;
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Drawable mDivider;
/**
* Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}.
*/
private int mOrientation;
private final Rect mBounds = new Rect();
/**
* Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a
* {@link LinearLayoutManager}.
*
* @param context Current context, it will be used to access resources.
* @param orientation Divider orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}.
*/
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
/**
* Sets the orientation for this divider. This should be called if
* {@link RecyclerView.LayoutManager} changes orientation.
*
* @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
*/
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL && orientation != VERTICAL) {
throw new IllegalArgumentException(
"Invalid orientation. It should be either HORIZONTAL or VERTICAL");
}
mOrientation = orientation;
}
/**
* Sets the {@link Drawable} for this divider.
*
* @param drawable Drawable that should be used as a divider.
*/
public void setDrawable(@NonNull Drawable drawable) {
if (drawable == null) {
throw new IllegalArgumentException("Drawable cannot be null.");
}
mDivider = drawable;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getLayoutManager() == null) {
return;
}
if (mOrientation == VERTICAL) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
private static void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
final Rect insets = lp.mDecorInsets;
outBounds.set(view.getLeft() - insets.left - lp.leftMargin,
view.getTop() - insets.top - lp.topMargin,
view.getRight() + insets.right + lp.rightMargin,
view.getBottom() + insets.bottom + lp.bottomMargin);
}
private void drawVertical(Canvas canvas, RecyclerView parent) {
canvas.save();
final int left = 0;
final int right = parent.getWidth();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
getDecoratedBoundsWithMargins(child, mBounds);
final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child));
final int top = bottom - mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
canvas.restore();
}
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
canvas.save();
final int top = 0;
final int bottom = parent.getHeight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
getDecoratedBoundsWithMargins(child, mBounds);
final int right = mBounds.right + Math.round(ViewCompat.getTranslationX(child));
final int left = right - mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
canvas.restore();
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
if (mOrientation == VERTICAL) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}

View file

@ -3,13 +3,19 @@ package be.digitalia.fosdem.adapters;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Parcelable;
import android.support.annotation.ColorInt;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import java.util.Date;
@ -17,20 +23,76 @@ import java.util.Date;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.db.DatabaseManager;
import be.digitalia.fosdem.model.Event;
import be.digitalia.fosdem.widgets.MultiChoiceHelper;
public class BookmarksAdapter extends EventsAdapter {
@ColorInt
private final int errorColor;
final MultiChoiceHelper multiChoiceHelper;
public BookmarksAdapter(Context context) {
super(context);
errorColor = ContextCompat.getColor(context, R.color.error_material);
public BookmarksAdapter(AppCompatActivity activity) {
super(activity);
errorColor = ContextCompat.getColor(activity, R.color.error_material);
multiChoiceHelper = new MultiChoiceHelper(activity, this);
multiChoiceHelper.setMultiChoiceModeListener(new MultiChoiceHelper.MultiChoiceModeListener() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.action_mode_bookmarks, menu);
return true;
}
private void updateSelectedCountDisplay(ActionMode mode) {
int count = multiChoiceHelper.getCheckedItemCount();
mode.setTitle(multiChoiceHelper.getContext().getResources().getQuantityString(R.plurals.selected, count, count));
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder holder = (ViewHolder) view.getTag();
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
updateSelectedCountDisplay(mode);
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.delete:
// Remove multiple bookmarks at once
new RemoveBookmarksAsyncTask().execute(multiChoiceHelper.getCheckedItemIds());
mode.finish();
return true;
}
return false;
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
updateSelectedCountDisplay(mode);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
});
}
public Parcelable onSaveInstanceState() {
return multiChoiceHelper.onSaveInstanceState();
}
public void onRestoreInstanceState(Parcelable state) {
multiChoiceHelper.onRestoreInstanceState(state);
}
public void onDestroyView() {
multiChoiceHelper.clearChoices();
}
@Override
public void onBindViewHolder(ViewHolder holder, Cursor cursor) {
final int position = cursor.getPosition();
Context context = holder.itemView.getContext();
Event event = DatabaseManager.toEvent(cursor, holder.event);
holder.event = event;
@ -60,12 +122,16 @@ public class BookmarksAdapter extends EventsAdapter {
holder.details.setText(details);
}
holder.details.setContentDescription(context.getString(R.string.details_content_description, detailsContentDescription));
// Enable MultiChoice selection and update checked state
holder.bind(multiChoiceHelper, position);
}
/**
* Checks if the current event is overlapping with the previous or next one.
* Warning: this methods will update the cursor's position.
*/
public static boolean isOverlapping(Cursor cursor, Date startTime, Date endTime) {
private static boolean isOverlapping(Cursor cursor, Date startTime, Date endTime) {
final int position = cursor.getPosition();
if ((startTime != null) && (position > 0) && cursor.moveToPosition(position - 1)) {
@ -86,4 +152,14 @@ public class BookmarksAdapter extends EventsAdapter {
return false;
}
static class RemoveBookmarksAsyncTask extends AsyncTask<long[], Void, Void> {
@Override
protected Void doInBackground(long[]... params) {
DatabaseManager.getInstance().removeBookmarks(params[0]);
return null;
}
}
}

View file

@ -1,9 +1,9 @@
package be.digitalia.fosdem.adapters;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.support.v4.widget.CursorAdapter;
import android.support.v4.widget.TextViewCompat;
import android.support.v7.widget.AppCompatDrawableManager;
import android.text.TextUtils;
@ -16,11 +16,13 @@ import java.text.DateFormat;
import java.util.Date;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.activities.EventDetailsActivity;
import be.digitalia.fosdem.db.DatabaseManager;
import be.digitalia.fosdem.model.Event;
import be.digitalia.fosdem.utils.DateUtils;
import be.digitalia.fosdem.widgets.MultiChoiceHelper;
public class EventsAdapter extends CursorAdapter {
public class EventsAdapter extends RecyclerViewCursorAdapter<EventsAdapter.ViewHolder> {
protected final LayoutInflater inflater;
protected final DateFormat timeDateFormat;
@ -31,7 +33,6 @@ public class EventsAdapter extends CursorAdapter {
}
public EventsAdapter(Context context, boolean showDay) {
super(context, null, 0);
inflater = LayoutInflater.from(context);
timeDateFormat = DateUtils.getTimeDateFormat(context);
this.showDay = showDay;
@ -43,22 +44,19 @@ public class EventsAdapter extends CursorAdapter {
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = inflater.inflate(R.layout.item_event, parent, false);
ViewHolder holder = new ViewHolder();
holder.title = (TextView) view.findViewById(R.id.title);
holder.persons = (TextView) view.findViewById(R.id.persons);
holder.trackName = (TextView) view.findViewById(R.id.track_name);
holder.details = (TextView) view.findViewById(R.id.details);
view.setTag(holder);
return view;
public int getItemViewType(int position) {
return R.layout.item_event;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder holder = (ViewHolder) view.getTag();
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.item_event, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, Cursor cursor) {
Context context = holder.itemView.getContext();
Event event = DatabaseManager.toEvent(cursor, holder.event);
holder.event = event;
@ -92,11 +90,29 @@ public class EventsAdapter extends CursorAdapter {
holder.details.setContentDescription(context.getString(R.string.details_content_description, details));
}
protected static class ViewHolder {
static class ViewHolder extends MultiChoiceHelper.ViewHolder implements View.OnClickListener {
TextView title;
TextView persons;
TextView trackName;
TextView details;
Event event;
public ViewHolder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.title);
persons = (TextView) itemView.findViewById(R.id.persons);
trackName = (TextView) itemView.findViewById(R.id.track_name);
details = (TextView) itemView.findViewById(R.id.details);
setOnClickListener(this);
}
@Override
public void onClick(View view) {
Context context = view.getContext();
Intent intent = new Intent(context, EventDetailsActivity.class)
.putExtra(EventDetailsActivity.EXTRA_EVENT, event);
context.startActivity(intent);
}
}
}

View file

@ -0,0 +1,78 @@
package be.digitalia.fosdem.adapters;
import android.database.Cursor;
import android.support.v7.widget.RecyclerView;
/**
* Simplified CursorAdapter designed for RecyclerView.
*
* @author Christophe Beyls
*/
public abstract class RecyclerViewCursorAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
private Cursor cursor;
private int rowIDColumn = -1;
public RecyclerViewCursorAdapter() {
setHasStableIds(true);
}
/**
* Swap in a new Cursor, returning the old Cursor.
* The old cursor is not closed.
*
* @return The previously set Cursor, if any.
* If the given new Cursor is the same instance as the previously set
* Cursor, null is also returned.
*/
public Cursor swapCursor(Cursor newCursor) {
if (newCursor == cursor) {
return null;
}
Cursor oldCursor = cursor;
cursor = newCursor;
rowIDColumn = (newCursor == null) ? -1 : newCursor.getColumnIndexOrThrow("_id");
notifyDataSetChanged();
return oldCursor;
}
public Cursor getCursor() {
return cursor;
}
@Override
public int getItemCount() {
return (cursor == null) ? 0 : cursor.getCount();
}
/**
* @return The cursor initialized to the specified position.
*/
public Object getItem(int position) {
if (cursor != null) {
cursor.moveToPosition(position);
}
return cursor;
}
@Override
public long getItemId(int position) {
if ((cursor != null) && cursor.moveToPosition(position)) {
return cursor.getLong(rowIDColumn);
}
return RecyclerView.NO_ID;
}
@Override
public void onBindViewHolder(VH holder, int position) {
if (cursor == null) {
throw new IllegalStateException("this should only be called when the cursor is not null");
}
if (!cursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
onBindViewHolder(holder, cursor);
}
public abstract void onBindViewHolder(VH holder, Cursor cursor);
}

View file

@ -1,17 +1,16 @@
package be.digitalia.fosdem.fragments;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.view.View;
import android.widget.ListView;
import be.digitalia.fosdem.activities.EventDetailsActivity;
import be.digitalia.fosdem.adapters.EventsAdapter;
import be.digitalia.fosdem.model.Event;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
public abstract class BaseLiveListFragment extends SmoothListFragment implements LoaderCallbacks<Cursor> {
import be.digitalia.fosdem.adapters.EventsAdapter;
public abstract class BaseLiveListFragment extends RecyclerViewFragment implements LoaderCallbacks<Cursor> {
private static final int EVENTS_LOADER_ID = 1;
@ -21,7 +20,13 @@ public abstract class BaseLiveListFragment extends SmoothListFragment implements
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
adapter = new EventsAdapter(getActivity(), false);
setListAdapter(adapter);
}
@Override
protected void onRecyclerViewCreated(RecyclerView recyclerView, Bundle savedInstanceState) {
recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext()));
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL));
recyclerView.setAdapter(adapter);
}
@Override
@ -29,7 +34,7 @@ public abstract class BaseLiveListFragment extends SmoothListFragment implements
super.onActivityCreated(savedInstanceState);
setEmptyText(getEmptyText());
setListShown(false);
setProgressBarVisible(true);
getLoaderManager().initLoader(EVENTS_LOADER_ID, null, this);
}
@ -42,18 +47,11 @@ public abstract class BaseLiveListFragment extends SmoothListFragment implements
adapter.swapCursor(data);
}
setListShown(true);
setProgressBarVisible(false);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Event event = adapter.getItem(position);
Intent intent = new Intent(getActivity(), EventDetailsActivity.class).putExtra(EventDetailsActivity.EXTRA_EVENT, event);
startActivity(intent);
}
}

View file

@ -1,40 +1,38 @@
package be.digitalia.fosdem.fragments;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.content.SharedPreferencesCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.activities.EventDetailsActivity;
import be.digitalia.fosdem.adapters.BookmarksAdapter;
import be.digitalia.fosdem.adapters.EventsAdapter;
import be.digitalia.fosdem.db.DatabaseManager;
import be.digitalia.fosdem.loaders.SimpleCursorLoader;
import be.digitalia.fosdem.model.Event;
import be.digitalia.fosdem.widgets.BookmarksMultiChoiceModeListener;
/**
* Bookmarks list, optionally filterable.
*
* @author Christophe Beyls
*/
public class BookmarksListFragment extends SmoothListFragment implements LoaderCallbacks<Cursor> {
public class BookmarksListFragment extends RecyclerViewFragment implements LoaderCallbacks<Cursor> {
private static final int BOOKMARKS_LOADER_ID = 1;
private static final String PREF_UPCOMING_ONLY = "bookmarks_upcoming_only";
private static final String STATE_ADAPTER = "adapter";
private EventsAdapter adapter;
private BookmarksAdapter adapter;
private boolean upcomingOnly;
private MenuItem filterMenuItem;
@ -44,33 +42,41 @@ public class BookmarksListFragment extends SmoothListFragment implements LoaderC
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
adapter = new BookmarksAdapter(getActivity());
setListAdapter(adapter);
adapter = new BookmarksAdapter((AppCompatActivity) getActivity());
if (savedInstanceState != null) {
adapter.onRestoreInstanceState(savedInstanceState.getParcelable(STATE_ADAPTER));
}
upcomingOnly = getActivity().getPreferences(Context.MODE_PRIVATE).getBoolean(PREF_UPCOMING_ONLY, false);
setHasOptionsMenu(true);
}
@Override
protected void onRecyclerViewCreated(RecyclerView recyclerView, Bundle savedInstanceState) {
recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext()));
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL));
recyclerView.setAdapter(adapter);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
BookmarksMultiChoiceModeListener.register(getListView());
}
setEmptyText(getString(R.string.no_bookmark));
setListShown(false);
setProgressBarVisible(true);
getLoaderManager().initLoader(BOOKMARKS_LOADER_ID, null, this);
}
@Override
public void onDestroyView() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
BookmarksMultiChoiceModeListener.unregister(getListView());
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(STATE_ADAPTER, adapter.onSaveInstanceState());
}
@Override
public void onDestroyView() {
adapter.onDestroyView();
super.onDestroyView();
}
@ -116,7 +122,7 @@ public class BookmarksListFragment extends SmoothListFragment implements LoaderC
private static class BookmarksLoader extends SimpleCursorLoader {
// Events that just started are still shown for 5 minutes
private static final long TIME_OFFSET = 5L * 60L * 1000L;
private static final long TIME_OFFSET = 5L * DateUtils.MINUTE_IN_MILLIS;
private final boolean upcomingOnly;
private final Handler handler;
@ -179,18 +185,11 @@ public class BookmarksListFragment extends SmoothListFragment implements LoaderC
adapter.swapCursor(data);
}
setListShown(true);
setProgressBarVisible(false);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Event event = adapter.getItem(position);
Intent intent = new Intent(getActivity(), EventDetailsActivity.class).putExtra(EventDetailsActivity.EXTRA_EVENT, event);
startActivity(intent);
}
}

View file

@ -7,22 +7,23 @@ import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v7.widget.ConcatAdapter;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.view.ViewGroup;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.activities.EventDetailsActivity;
import be.digitalia.fosdem.adapters.EventsAdapter;
import be.digitalia.fosdem.db.DatabaseManager;
import be.digitalia.fosdem.loaders.SimpleCursorLoader;
import be.digitalia.fosdem.model.Event;
import be.digitalia.fosdem.model.Person;
public class PersonInfoListFragment extends SmoothListFragment implements LoaderCallbacks<Cursor> {
public class PersonInfoListFragment extends RecyclerViewFragment implements LoaderCallbacks<Cursor> {
private static final int PERSON_EVENTS_LOADER_ID = 1;
private static final String ARG_PERSON = "person";
@ -63,23 +64,23 @@ public class PersonInfoListFragment extends SmoothListFragment implements Loader
return false;
}
@Override
protected void onRecyclerViewCreated(RecyclerView recyclerView, Bundle savedInstanceState) {
final int contentMargin = getResources().getDimensionPixelSize(R.dimen.content_margin);
recyclerView.setPadding(contentMargin, contentMargin, contentMargin, contentMargin);
recyclerView.setClipToPadding(false);
recyclerView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext()));
recyclerView.setAdapter(new ConcatAdapter(new HeaderAdapter(), adapter));
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setEmptyText(getString(R.string.no_data));
int contentMargin = getResources().getDimensionPixelSize(R.dimen.content_margin);
ListView listView = getListView();
listView.setPadding(contentMargin, contentMargin, contentMargin, contentMargin);
listView.setClipToPadding(false);
listView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
View headerView = LayoutInflater.from(getActivity()).inflate(R.layout.header_person_info, null);
getListView().addHeaderView(headerView, null, false);
setListAdapter(adapter);
setListShown(false);
setProgressBarVisible(true);
getLoaderManager().initLoader(PERSON_EVENTS_LOADER_ID, null, this);
}
@ -110,7 +111,7 @@ public class PersonInfoListFragment extends SmoothListFragment implements Loader
adapter.swapCursor(data);
}
setListShown(true);
setProgressBarVisible(false);
}
@Override
@ -118,10 +119,34 @@ public class PersonInfoListFragment extends SmoothListFragment implements Loader
adapter.swapCursor(null);
}
static class HeaderAdapter extends RecyclerView.Adapter<HeaderAdapter.ViewHolder> {
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Event event = adapter.getItem(position - 1);
Intent intent = new Intent(getActivity(), EventDetailsActivity.class).putExtra(EventDetailsActivity.EXTRA_EVENT, event);
startActivity(intent);
public int getItemCount() {
return 1;
}
@Override
public int getItemViewType(int position) {
return R.layout.header_person_info;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.header_person_info, null);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// Nothing to bind
}
static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
}
}
}
}

View file

@ -0,0 +1,10 @@
package be.digitalia.fosdem.fragments;
import android.support.v7.widget.RecyclerView;
/**
* Components implementing this interface allow to share a RecycledViewPool between similar fragments.
*/
public interface RecycledViewPoolProvider {
RecyclerView.RecycledViewPool getRecycledViewPool();
}

View file

@ -0,0 +1,191 @@
package be.digitalia.fosdem.fragments;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.widgets.ContentLoadingProgressBar;
/**
* Fragment providing a RecyclerView, an empty view and a progress bar.
*
* @author Christophe Beyls
*/
public class RecyclerViewFragment extends Fragment {
private static final int DEFAULT_EMPTY_VIEW_PADDING_DIPS = 16;
static class ViewHolder {
FrameLayout container;
RecyclerView recyclerView;
View emptyView;
ContentLoadingProgressBar progress;
}
private class EmptyViewAwareRecyclerView extends RecyclerView {
private final AdapterDataObserver mEmptyObserver = new AdapterDataObserver() {
@Override
public void onChanged() {
updateEmptyViewVisibility();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
updateEmptyViewVisibility();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
updateEmptyViewVisibility();
}
};
public EmptyViewAwareRecyclerView(Context context) {
super(context);
}
public EmptyViewAwareRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public EmptyViewAwareRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void setAdapter(Adapter adapter) {
final Adapter oldAdapter = getAdapter();
if (oldAdapter != null) {
oldAdapter.unregisterAdapterDataObserver(mEmptyObserver);
}
super.setAdapter(adapter);
if (adapter != null) {
adapter.registerAdapterDataObserver(mEmptyObserver);
}
updateEmptyViewVisibility();
}
}
private ViewHolder mHolder;
private boolean mIsProgressBarVisible;
/**
* Override this method to provide a custom Empty View.
* The default one is a TextView with some padding.
*/
@NonNull
protected View onCreateEmptyView(LayoutInflater inflater, ViewGroup container, @Nullable Bundle savedInstanceState) {
TextView textView = new TextView(inflater.getContext());
textView.setGravity(Gravity.CENTER);
int textPadding = (int) (getResources().getDisplayMetrics().density * DEFAULT_EMPTY_VIEW_PADDING_DIPS);
textView.setPadding(textPadding, textPadding, textPadding, textPadding);
return textView;
}
/**
* Override this method to setup the RecyclerView (LayoutManager, ItemDecoration, Adapter)
*/
protected void onRecyclerViewCreated(RecyclerView recyclerView, @Nullable Bundle savedInstanceState) {
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
final Context context = inflater.getContext();
mHolder = new ViewHolder();
mHolder.container = new FrameLayout(context);
mHolder.recyclerView = new EmptyViewAwareRecyclerView(context, null, R.attr.recyclerViewStyle);
mHolder.recyclerView.setId(android.R.id.list);
mHolder.recyclerView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mHolder.recyclerView.setHasFixedSize(true);
mHolder.container.addView(mHolder.recyclerView,
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mHolder.emptyView = onCreateEmptyView(inflater, mHolder.container, savedInstanceState);
mHolder.emptyView.setId(android.R.id.empty);
mHolder.emptyView.setVisibility(View.GONE);
mHolder.container.addView(mHolder.emptyView,
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mHolder.progress = new ContentLoadingProgressBar(context, null, android.R.attr.progressBarStyleLarge);
mHolder.progress.setId(android.R.id.progress);
mHolder.progress.hide();
mHolder.container.addView(mHolder.progress,
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
mHolder.container.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
onRecyclerViewCreated(mHolder.recyclerView, savedInstanceState);
return mHolder.container;
}
@Override
public void onDestroyView() {
// Ensure the RecyclerView is properly unregistered as an observer of the adapter
mHolder.recyclerView.setAdapter(null);
mHolder = null;
mIsProgressBarVisible = false;
super.onDestroyView();
}
/**
* Get the fragments's RecyclerView widget.
*/
public RecyclerView getRecyclerView() {
return mHolder.recyclerView;
}
/**
* The default content for a RecyclerViewFragment has a TextView that can be shown when the list is empty.
* Call this method to supply the text it should use.
*/
public void setEmptyText(CharSequence text) {
((TextView) mHolder.emptyView).setText(text);
}
void updateEmptyViewVisibility() {
if (!mIsProgressBarVisible) {
RecyclerView.Adapter adapter = mHolder.recyclerView.getAdapter();
final boolean isEmptyViewVisible = (adapter != null) && (adapter.getItemCount() == 0);
mHolder.recyclerView.setVisibility(isEmptyViewVisible ? View.GONE : View.VISIBLE);
mHolder.emptyView.setVisibility(isEmptyViewVisible ? View.VISIBLE : View.GONE);
}
}
/**
* Call this method to show or hide the indeterminate progress bar.
* When shown, the RecyclerView will be hidden.
*
* @param visible true to show the progress bar, false to hide it. The initial value is false.
*/
public void setProgressBarVisible(boolean visible) {
if (mIsProgressBarVisible != visible) {
mIsProgressBarVisible = visible;
if (visible) {
mHolder.recyclerView.setVisibility(View.GONE);
mHolder.emptyView.setVisibility(View.GONE);
mHolder.progress.show();
} else {
updateEmptyViewVisibility();
mHolder.progress.hide();
}
}
}
}

View file

@ -1,21 +1,20 @@
package be.digitalia.fosdem.fragments;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.view.View;
import android.widget.ListView;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.activities.EventDetailsActivity;
import be.digitalia.fosdem.adapters.EventsAdapter;
import be.digitalia.fosdem.db.DatabaseManager;
import be.digitalia.fosdem.loaders.SimpleCursorLoader;
import be.digitalia.fosdem.model.Event;
public class SearchResultListFragment extends SmoothListFragment implements LoaderCallbacks<Cursor> {
public class SearchResultListFragment extends RecyclerViewFragment implements LoaderCallbacks<Cursor> {
private static final int EVENTS_LOADER_ID = 1;
private static final String ARG_QUERY = "query";
@ -34,7 +33,13 @@ public class SearchResultListFragment extends SmoothListFragment implements Load
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
adapter = new EventsAdapter(getActivity());
setListAdapter(adapter);
}
@Override
protected void onRecyclerViewCreated(RecyclerView recyclerView, Bundle savedInstanceState) {
recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext()));
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL));
recyclerView.setAdapter(adapter);
}
@Override
@ -42,7 +47,7 @@ public class SearchResultListFragment extends SmoothListFragment implements Load
super.onActivityCreated(savedInstanceState);
setEmptyText(getString(R.string.no_search_result));
setListShown(false);
setProgressBarVisible(true);
getLoaderManager().initLoader(EVENTS_LOADER_ID, null, this);
}
@ -74,18 +79,11 @@ public class SearchResultListFragment extends SmoothListFragment implements Load
adapter.swapCursor(data);
}
setListShown(true);
setProgressBarVisible(false);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Event event = adapter.getItem(position);
Intent intent = new Intent(getActivity(), EventDetailsActivity.class).putExtra(EventDetailsActivity.EXTRA_EVENT, event);
startActivity(intent);
}
}

View file

@ -59,7 +59,7 @@ public class SmoothListFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
final Context context = getActivity();
final Context context = inflater.getContext();
mHolder = new ViewHolder();
@ -138,7 +138,7 @@ public class SmoothListFragment extends Fragment {
}
/**
* The default content for a SwipeRefreshListFragment has a TextView that can be shown when the list is empty.
* The default content for a SmoothListFragment has a TextView that can be shown when the list is empty.
* Call this method to supply the text it should use.
*/
public void setEmptyText(CharSequence text) {

View file

@ -14,20 +14,20 @@ import android.support.v4.content.Loader;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.content.SharedPreferencesCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import be.digitalia.fosdem.widgets.SlidingTabLayout;
import java.util.List;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.db.DatabaseManager;
import be.digitalia.fosdem.loaders.GlobalCacheLoader;
import be.digitalia.fosdem.model.Day;
import be.digitalia.fosdem.widgets.SlidingTabLayout;
public class TracksFragment extends Fragment implements LoaderCallbacks<List<Day>> {
public class TracksFragment extends Fragment implements RecycledViewPoolProvider, LoaderCallbacks<List<Day>> {
static class ViewHolder {
View contentView;
@ -35,6 +35,7 @@ public class TracksFragment extends Fragment implements LoaderCallbacks<List<Day
ViewPager pager;
SlidingTabLayout slidingTabs;
DaysAdapter daysAdapter;
RecyclerView.RecycledViewPool recycledViewPool;
}
private static final int DAYS_LOADER_ID = 1;
@ -63,6 +64,7 @@ public class TracksFragment extends Fragment implements LoaderCallbacks<List<Day
holder.pager = (ViewPager) view.findViewById(R.id.pager);
holder.slidingTabs = (SlidingTabLayout) view.findViewById(R.id.sliding_tabs);
holder.daysAdapter = new DaysAdapter(getChildFragmentManager());
holder.recycledViewPool = new RecyclerView.RecycledViewPool();
return view;
}
@ -93,6 +95,11 @@ public class TracksFragment extends Fragment implements LoaderCallbacks<List<Day
}
}
@Override
public RecyclerView.RecycledViewPool getRecycledViewPool() {
return (holder == null) ? null : holder.recycledViewPool;
}
private static class DaysLoader extends GlobalCacheLoader<List<Day>> {
private final BroadcastReceiver scheduleRefreshedReceiver = new BroadcastReceiver() {

View file

@ -4,28 +4,31 @@ import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.TextView;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.activities.TrackScheduleActivity;
import be.digitalia.fosdem.adapters.RecyclerViewCursorAdapter;
import be.digitalia.fosdem.db.DatabaseManager;
import be.digitalia.fosdem.loaders.SimpleCursorLoader;
import be.digitalia.fosdem.model.Day;
import be.digitalia.fosdem.model.Track;
public class TracksListFragment extends SmoothListFragment implements LoaderCallbacks<Cursor> {
public class TracksListFragment extends RecyclerViewFragment implements LoaderCallbacks<Cursor> {
private static final int TRACKS_LOADER_ID = 1;
private static final String ARG_DAY = "day";
private Day day;
Day day;
private TracksAdapter adapter;
public static TracksListFragment newInstance(Day day) {
@ -39,9 +42,20 @@ public class TracksListFragment extends SmoothListFragment implements LoaderCall
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
adapter = new TracksAdapter(getActivity());
adapter = new TracksAdapter();
day = getArguments().getParcelable(ARG_DAY);
setListAdapter(adapter);
}
@Override
protected void onRecyclerViewCreated(RecyclerView recyclerView, Bundle savedInstanceState) {
Fragment parentFragment = getParentFragment();
if (parentFragment instanceof RecycledViewPoolProvider) {
recyclerView.setRecycledViewPool(((RecycledViewPoolProvider) parentFragment).getRecycledViewPool());
}
recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext()));
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL));
recyclerView.setAdapter(adapter);
}
@Override
@ -49,7 +63,7 @@ public class TracksListFragment extends SmoothListFragment implements LoaderCall
super.onActivityCreated(savedInstanceState);
setEmptyText(getString(R.string.no_data));
setListShown(false);
setProgressBarVisible(true);
getLoaderManager().initLoader(TRACKS_LOADER_ID, null, this);
}
@ -80,7 +94,7 @@ public class TracksListFragment extends SmoothListFragment implements LoaderCall
adapter.swapCursor(data);
}
setListShown(true);
setProgressBarVisible(false);
}
@Override
@ -88,21 +102,12 @@ public class TracksListFragment extends SmoothListFragment implements LoaderCall
adapter.swapCursor(null);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Track track = adapter.getItem(position);
Intent intent = new Intent(getActivity(), TrackScheduleActivity.class).putExtra(TrackScheduleActivity.EXTRA_DAY, day).putExtra(
TrackScheduleActivity.EXTRA_TRACK, track);
startActivity(intent);
}
private static class TracksAdapter extends CursorAdapter {
private class TracksAdapter extends RecyclerViewCursorAdapter<TrackViewHolder> {
private final LayoutInflater inflater;
public TracksAdapter(Context context) {
super(context, null, 0);
inflater = LayoutInflater.from(context);
public TracksAdapter() {
inflater = LayoutInflater.from(getContext());
}
@Override
@ -111,29 +116,41 @@ public class TracksListFragment extends SmoothListFragment implements LoaderCall
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
public TrackViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.simple_list_item_2_material, parent, false);
ViewHolder holder = new ViewHolder();
holder.name = (TextView) view.findViewById(android.R.id.text1);
holder.type = (TextView) view.findViewById(android.R.id.text2);
view.setTag(holder);
return view;
return new TrackViewHolder(view);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder holder = (ViewHolder) view.getTag();
public void onBindViewHolder(TrackViewHolder holder, Cursor cursor) {
holder.day = day;
holder.track = DatabaseManager.toTrack(cursor, holder.track);
holder.name.setText(holder.track.getName());
holder.type.setText(holder.track.getType().getNameResId());
}
}
static class ViewHolder {
static class TrackViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView name;
TextView type;
Day day;
Track track;
TrackViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(android.R.id.text1);
type = (TextView) itemView.findViewById(android.R.id.text2);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
Context context = view.getContext();
Intent intent = new Intent(context, TrackScheduleActivity.class)
.putExtra(TrackScheduleActivity.EXTRA_DAY, day)
.putExtra(TrackScheduleActivity.EXTRA_TRACK, track);
context.startActivity(intent);
}
}
}

View file

@ -1,87 +0,0 @@
package be.digitalia.fosdem.widgets;
import android.annotation.TargetApi;
import android.os.AsyncTask;
import android.os.Build;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.AbsListView;
import android.widget.AbsListView.MultiChoiceModeListener;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.db.DatabaseManager;
/**
* Context menu for the bookmarks list items, available for API 11+ only.
*
* @author Christophe Beyls
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class BookmarksMultiChoiceModeListener implements MultiChoiceModeListener {
private AbsListView listView;
public static void register(AbsListView listView) {
listView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE_MODAL);
BookmarksMultiChoiceModeListener listener = new BookmarksMultiChoiceModeListener(listView);
listView.setMultiChoiceModeListener(listener);
}
public static void unregister(AbsListView listView) {
// Will close the ActionMode if open
listView.setChoiceMode(AbsListView.CHOICE_MODE_NONE);
}
private BookmarksMultiChoiceModeListener(AbsListView listView) {
this.listView = listView;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.action_mode_bookmarks, menu);
return true;
}
private void updateSelectedCountDisplay(ActionMode mode) {
int count = listView.getCheckedItemCount();
mode.setTitle(listView.getContext().getResources().getQuantityString(R.plurals.selected, count, count));
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
updateSelectedCountDisplay(mode);
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.delete:
// Remove multiple bookmarks at once
new RemoveBookmarksAsyncTask().execute(listView.getCheckedItemIds());
mode.finish();
return true;
}
return false;
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
updateSelectedCountDisplay(mode);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
static class RemoveBookmarksAsyncTask extends AsyncTask<long[], Void, Void> {
@Override
protected Void doInBackground(long[]... params) {
DatabaseManager.getInstance().removeBookmarks(params[0]);
return null;
}
}
}

View file

@ -0,0 +1,43 @@
package be.digitalia.fosdem.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Checkable;
/**
* {@link android.widget.LinearLayout} implementing the {@link android.widget.Checkable}
* interface by keeping an internal 'checked' state flag.
*/
public class CheckableLinearLayout extends ForegroundLinearLayout implements Checkable {
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
private boolean mChecked = false;
public CheckableLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public boolean isChecked() {
return mChecked;
}
public void setChecked(boolean b) {
if (b != mChecked) {
mChecked = b;
refreshDrawableState();
}
}
public void toggle() {
setChecked(!mChecked);
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
}

View file

@ -0,0 +1,231 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package be.digitalia.fosdem.widgets;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v7.widget.LinearLayoutCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import be.digitalia.fosdem.R;
public class ForegroundLinearLayout extends LinearLayoutCompat {
private Drawable mForeground;
private final Rect mSelfBounds = new Rect();
private final Rect mOverlayBounds = new Rect();
private int mForegroundGravity = Gravity.FILL;
protected boolean mForegroundInPadding = true;
boolean mForegroundBoundsChanged = false;
public ForegroundLinearLayout(Context context) {
this(context, null);
}
public ForegroundLinearLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ForegroundLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundLinearLayout,
defStyle, 0);
mForegroundGravity = a.getInt(
R.styleable.ForegroundLinearLayout_android_foregroundGravity, mForegroundGravity);
final Drawable d = a.getDrawable(R.styleable.ForegroundLinearLayout_android_foreground);
if (d != null) {
setForeground(d);
}
mForegroundInPadding = a.getBoolean(
R.styleable.ForegroundLinearLayout_foregroundInsidePadding, true);
a.recycle();
}
/**
* Describes how the foreground is positioned.
*
* @return foreground gravity.
* @see #setForegroundGravity(int)
*/
public int getForegroundGravity() {
return mForegroundGravity;
}
/**
* Describes how the foreground is positioned. Defaults to START and TOP.
*
* @param foregroundGravity See {@link Gravity}
* @see #getForegroundGravity()
*/
public void setForegroundGravity(int foregroundGravity) {
if (mForegroundGravity != foregroundGravity) {
if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
foregroundGravity |= Gravity.START;
}
if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
foregroundGravity |= Gravity.TOP;
}
mForegroundGravity = foregroundGravity;
if (mForegroundGravity == Gravity.FILL && mForeground != null) {
Rect padding = new Rect();
mForeground.getPadding(padding);
}
requestLayout();
}
}
@Override
protected boolean verifyDrawable(@NonNull Drawable who) {
return super.verifyDrawable(who) || (who == mForeground);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mForeground != null) {
mForeground.jumpToCurrentState();
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mForeground != null && mForeground.isStateful()) {
mForeground.setState(getDrawableState());
}
}
/**
* Supply a Drawable that is to be rendered on top of all of the child
* views in the frame layout. Any padding in the Drawable will be taken
* into account by ensuring that the children are inset to be placed
* inside of the padding area.
*
* @param drawable The Drawable to be drawn on top of the children.
*/
public void setForeground(Drawable drawable) {
if (mForeground != drawable) {
if (mForeground != null) {
mForeground.setCallback(null);
unscheduleDrawable(mForeground);
}
mForeground = drawable;
if (drawable != null) {
setWillNotDraw(false);
drawable.setCallback(this);
if (drawable.isStateful()) {
drawable.setState(getDrawableState());
}
if (mForegroundGravity == Gravity.FILL) {
Rect padding = new Rect();
drawable.getPadding(padding);
}
} else {
setWillNotDraw(true);
}
requestLayout();
invalidate();
}
}
/**
* Returns the drawable used as the foreground of this FrameLayout. The
* foreground drawable, if non-null, is always drawn on top of the children.
*
* @return A Drawable or null if no foreground was set.
*/
public Drawable getForeground() {
return mForeground;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mForegroundBoundsChanged |= changed;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mForegroundBoundsChanged = true;
}
@Override
public void draw(@NonNull Canvas canvas) {
super.draw(canvas);
if (mForeground != null) {
final Drawable foreground = mForeground;
if (mForegroundBoundsChanged) {
mForegroundBoundsChanged = false;
final Rect selfBounds = mSelfBounds;
final Rect overlayBounds = mOverlayBounds;
final int w = getRight() - getLeft();
final int h = getBottom() - getTop();
if (mForegroundInPadding) {
selfBounds.set(0, 0, w, h);
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
w - getPaddingRight(), h - getPaddingBottom());
}
Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void drawableHotspotChanged(float x, float y) {
super.drawableHotspotChanged(x, y);
if (mForeground != null) {
mForeground.setHotspot(x, y);
}
}
}

View file

@ -0,0 +1,483 @@
package be.digitalia.fosdem.widgets;
import android.content.Context;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.v4.util.LongSparseArray;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.RecyclerView;
import android.util.SparseBooleanArray;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Checkable;
/**
* Helper class to reproduce ListView's modal MultiChoice mode with a RecyclerView.
* Compatible with API 7+.
* Declare and use this class from inside your Adapter.
*
* @author Christophe Beyls
*/
public class MultiChoiceHelper {
/**
* A handy ViewHolder base class which works with the MultiChoiceHelper
* and reproduces the default behavior of a ListView.
*/
public static abstract class ViewHolder extends RecyclerView.ViewHolder {
View.OnClickListener clickListener;
MultiChoiceHelper multiChoiceHelper;
public ViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (isMultiChoiceActive()) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
multiChoiceHelper.toggleItemChecked(position, false);
updateCheckedState(position);
}
} else {
if (clickListener != null) {
clickListener.onClick(view);
}
}
}
});
itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if ((multiChoiceHelper == null) || isMultiChoiceActive()) {
return false;
}
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
multiChoiceHelper.setItemChecked(position, true, false);
updateCheckedState(position);
}
return true;
}
});
}
void updateCheckedState(int position) {
final boolean isChecked = multiChoiceHelper.isItemChecked(position);
if (itemView instanceof Checkable) {
((Checkable) itemView).setChecked(isChecked);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
itemView.setActivated(isChecked);
}
}
public void setOnClickListener(View.OnClickListener clickListener) {
this.clickListener = clickListener;
}
public void bind(MultiChoiceHelper multiChoiceHelper, int position) {
this.multiChoiceHelper = multiChoiceHelper;
if (multiChoiceHelper != null) {
updateCheckedState(position);
}
}
public boolean isMultiChoiceActive() {
return (multiChoiceHelper != null) && (multiChoiceHelper.getCheckedItemCount() > 0);
}
}
public interface MultiChoiceModeListener extends ActionMode.Callback {
/**
* Called when an item is checked or unchecked during selection mode.
*
* @param mode The {@link ActionMode} providing the selection startSupportActionModemode
* @param position Adapter position of the item that was checked or unchecked
* @param id Adapter ID of the item that was checked or unchecked
* @param checked <code>true</code> if the item is now checked, <code>false</code>
* if the item is now unchecked.
*/
void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked);
}
private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
private final AppCompatActivity activity;
private final RecyclerView.Adapter adapter;
private SparseBooleanArray checkStates;
private LongSparseArray<Integer> checkedIdStates;
private int checkedItemCount = 0;
private MultiChoiceModeWrapper multiChoiceModeCallback;
ActionMode choiceActionMode;
/**
* Make sure this constructor is called before setting the adapter on the RecyclerView
* so this class will be notified before the RecyclerView in case of data set changes.
*/
public MultiChoiceHelper(@NonNull AppCompatActivity activity, @NonNull RecyclerView.Adapter adapter) {
this.activity = activity;
this.adapter = adapter;
adapter.registerAdapterDataObserver(new AdapterDataSetObserver());
checkStates = new SparseBooleanArray(0);
if (adapter.hasStableIds()) {
checkedIdStates = new LongSparseArray<>(0);
}
}
public Context getContext() {
return activity;
}
public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
if (listener == null) {
multiChoiceModeCallback = null;
return;
}
if (multiChoiceModeCallback == null) {
multiChoiceModeCallback = new MultiChoiceModeWrapper();
}
multiChoiceModeCallback.setWrapped(listener);
}
public int getCheckedItemCount() {
return checkedItemCount;
}
public boolean isItemChecked(int position) {
return checkStates.get(position);
}
public SparseBooleanArray getCheckedItemPositions() {
return checkStates;
}
public long[] getCheckedItemIds() {
final LongSparseArray<Integer> idStates = checkedIdStates;
if (idStates == null) {
return new long[0];
}
final int count = idStates.size();
final long[] ids = new long[count];
for (int i = 0; i < count; i++) {
ids[i] = idStates.keyAt(i);
}
return ids;
}
public void clearChoices() {
if (checkedItemCount > 0) {
final int start = checkStates.keyAt(0);
final int end = checkStates.keyAt(checkStates.size() - 1);
checkStates.clear();
if (checkedIdStates != null) {
checkedIdStates.clear();
}
checkedItemCount = 0;
adapter.notifyItemRangeChanged(start, end - start + 1);
if (choiceActionMode != null) {
choiceActionMode.finish();
}
}
}
public void setItemChecked(int position, boolean value, boolean notifyChanged) {
// Start selection mode if needed. We don't need to if we're unchecking something.
if (value) {
startSupportActionModeIfNeeded();
}
boolean oldValue = checkStates.get(position);
checkStates.put(position, value);
if (oldValue != value) {
final long id = adapter.getItemId(position);
if (checkedIdStates != null) {
if (value) {
checkedIdStates.put(id, position);
} else {
checkedIdStates.delete(id);
}
}
if (value) {
checkedItemCount++;
} else {
checkedItemCount--;
}
if (notifyChanged) {
adapter.notifyItemChanged(position);
}
if (choiceActionMode != null) {
multiChoiceModeCallback.onItemCheckedStateChanged(choiceActionMode, position, id, value);
if (checkedItemCount == 0) {
choiceActionMode.finish();
}
}
}
}
public void toggleItemChecked(int position, boolean notifyChanged) {
setItemChecked(position, !isItemChecked(position), notifyChanged);
}
public Parcelable onSaveInstanceState() {
SavedState savedState = new SavedState();
savedState.checkedItemCount = checkedItemCount;
savedState.checkStates = clone(checkStates);
if (checkedIdStates != null) {
savedState.checkedIdStates = checkedIdStates.clone();
}
return savedState;
}
private static SparseBooleanArray clone(SparseBooleanArray original) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return original.clone();
}
final int size = original.size();
SparseBooleanArray clone = new SparseBooleanArray(size);
for (int i = 0; i < size; ++i) {
clone.append(original.keyAt(i), original.valueAt(i));
}
return clone;
}
public void onRestoreInstanceState(Parcelable state) {
if ((state != null) && (checkedItemCount == 0)) {
SavedState savedState = (SavedState) state;
checkedItemCount = savedState.checkedItemCount;
checkStates = savedState.checkStates;
checkedIdStates = savedState.checkedIdStates;
if (checkedItemCount > 0) {
// Empty adapter is given a chance to be populated before completeRestoreInstanceState()
if (adapter.getItemCount() > 0) {
confirmCheckedPositions();
}
activity.getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
completeRestoreInstanceState();
}
});
}
}
}
void completeRestoreInstanceState() {
if (checkedItemCount > 0) {
if (adapter.getItemCount() == 0) {
// Adapter was not populated, clear the selection
confirmCheckedPositions();
} else {
startSupportActionModeIfNeeded();
}
}
}
private void startSupportActionModeIfNeeded() {
if (choiceActionMode == null) {
if (multiChoiceModeCallback == null) {
throw new IllegalStateException("No callback set");
}
choiceActionMode = activity.startSupportActionMode(multiChoiceModeCallback);
}
}
public static class SavedState implements Parcelable {
int checkedItemCount;
SparseBooleanArray checkStates;
LongSparseArray<Integer> checkedIdStates;
SavedState() {
}
SavedState(Parcel in) {
checkedItemCount = in.readInt();
checkStates = in.readSparseBooleanArray();
final int n = in.readInt();
if (n >= 0) {
checkedIdStates = new LongSparseArray<>(n);
for (int i = 0; i < n; i++) {
final long key = in.readLong();
final int value = in.readInt();
checkedIdStates.append(key, value);
}
}
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(checkedItemCount);
out.writeSparseBooleanArray(checkStates);
final int n = checkedIdStates != null ? checkedIdStates.size() : -1;
out.writeInt(n);
for (int i = 0; i < n; i++) {
out.writeLong(checkedIdStates.keyAt(i));
out.writeInt(checkedIdStates.valueAt(i));
}
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
void confirmCheckedPositions() {
if (checkedItemCount == 0) {
return;
}
final int itemCount = adapter.getItemCount();
boolean checkedCountChanged = false;
if (itemCount == 0) {
// Optimized path for empty adapter: remove all items.
checkStates.clear();
if (checkedIdStates != null) {
checkedIdStates.clear();
}
checkedItemCount = 0;
checkedCountChanged = true;
} else if (checkedIdStates != null) {
// Clear out the positional check states, we'll rebuild it below from IDs.
checkStates.clear();
for (int checkedIndex = 0; checkedIndex < checkedIdStates.size(); checkedIndex++) {
final long id = checkedIdStates.keyAt(checkedIndex);
final int lastPos = checkedIdStates.valueAt(checkedIndex);
if ((lastPos >= itemCount) || (id != adapter.getItemId(lastPos))) {
// Look around to see if the ID is nearby. If not, uncheck it.
final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, itemCount);
boolean found = false;
for (int searchPos = start; searchPos < end; searchPos++) {
final long searchId = adapter.getItemId(searchPos);
if (id == searchId) {
found = true;
checkStates.put(searchPos, true);
checkedIdStates.setValueAt(checkedIndex, searchPos);
break;
}
}
if (!found) {
checkedIdStates.delete(id);
checkedIndex--;
checkedItemCount--;
checkedCountChanged = true;
if (choiceActionMode != null && multiChoiceModeCallback != null) {
multiChoiceModeCallback.onItemCheckedStateChanged(choiceActionMode, lastPos, id, false);
}
}
} else {
checkStates.put(lastPos, true);
}
}
} else {
// If the total number of items decreased, remove all out-of-range check indexes.
for (int i = checkStates.size() - 1; (i >= 0) && (checkStates.keyAt(i) >= itemCount); i--) {
if (checkStates.valueAt(i)) {
checkedItemCount--;
checkedCountChanged = true;
}
checkStates.delete(checkStates.keyAt(i));
}
}
if (checkedCountChanged && choiceActionMode != null) {
if (checkedItemCount == 0) {
choiceActionMode.finish();
} else {
choiceActionMode.invalidate();
}
}
}
class AdapterDataSetObserver extends RecyclerView.AdapterDataObserver {
@Override
public void onChanged() {
confirmCheckedPositions();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
confirmCheckedPositions();
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
confirmCheckedPositions();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
confirmCheckedPositions();
}
}
class MultiChoiceModeWrapper implements MultiChoiceModeListener {
private MultiChoiceModeListener wrapped;
public void setWrapped(@NonNull MultiChoiceModeListener wrapped) {
this.wrapped = wrapped;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return wrapped.onCreateActionMode(mode, menu);
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return wrapped.onPrepareActionMode(mode, menu);
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return wrapped.onActionItemClicked(mode, item);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
wrapped.onDestroyActionMode(mode);
choiceActionMode = null;
clearChoices();
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
wrapped.onItemCheckedStateChanged(mode, position, id, checked);
}
}
}

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<shape>
<solid android:color="@color/fosdem_blue_translucent"/>
</shape>
</item>
<item android:drawable="@android:color/transparent"/>
</selector>

View file

@ -1,9 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<be.digitalia.fosdem.widgets.CheckableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/activatedBackgroundIndicator"
android:background="?attr/selectableItemBackground"
android:focusable="true"
android:foreground="@drawable/checkable_foreground"
android:orientation="vertical"
android:paddingBottom="@dimen/list_item_padding"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
@ -41,4 +44,4 @@
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Saturday, 09:30 - 09:55 | Janson"/>
</LinearLayout>
</be.digitalia.fosdem.widgets.CheckableLinearLayout>

View file

@ -2,6 +2,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:focusable="true"
android:gravity="center_vertical"
android:minHeight="?attr/listPreferredItemHeight"
android:orientation="vertical"

View file

@ -3,6 +3,7 @@
<!-- Drawable used as a background for activated items. -->
<attr name="activatedBackgroundIndicator" format="reference"/>
<attr name="recyclerViewStyle" format="reference"/>
<declare-styleable name="SlidingTabLayout">
<attr name="indicatorColor" format="color"/>
@ -26,6 +27,12 @@
<attr name="insetForeground" format="color|reference"/>
</declare-styleable>
<declare-styleable name="ForegroundLinearLayout">
<attr name="android:foreground"/>
<attr name="android:foregroundGravity"/>
<attr name="foregroundInsidePadding" format="boolean"/>
</declare-styleable>
<declare-styleable name="PrimaryTextColors">
<attr name="android:textColorPrimary"/>
<attr name="android:textColorPrimaryInverse"/>

View file

@ -11,6 +11,7 @@
<item name="android:windowContentOverlay">@null</item>
<item name="activatedBackgroundIndicator">@drawable/activated_background</item>
<item name="recyclerViewStyle">@style/RecyclerView</item>
</style>
<style name="AppTheme" parent="Base.AppTheme">
@ -61,6 +62,10 @@
<!-- Styles -->
<style name="RecyclerView" parent="android:Widget">
<item name="android:scrollbars">vertical</item>
</style>
<style name="Widget.Design.ScrimInsetsFrameLayout" parent="">
<item name="insetForeground">#4000</item>
</style>