Niedzielski has uploaded a new change for review. https://gerrit.wikimedia.org/r/292054
Change subject: Add header & footer View support to Feed list card ...................................................................... Add header & footer View support to Feed list card The continue reading card, and other cards, have a header View that is a little different than their item views. Add support for header and footer Views without requiring adapter index and type logic. Bug: T130963 Change-Id: I53bbf5dc70223c2775e83f38df49cce6480c7700 --- A app/src/main/java/org/wikipedia/feed/view/CardFooterView.java A app/src/main/java/org/wikipedia/feed/view/CardHeaderView.java M app/src/main/java/org/wikipedia/feed/view/ListCardView.java M app/src/main/java/org/wikipedia/views/DrawableItemDecoration.java M app/src/main/java/org/wikipedia/views/MarginItemDecoration.java M app/src/main/java/org/wikipedia/views/ViewUtil.java A app/src/main/res/drawable/ic_arrow_forward_black_24dp.xml A app/src/main/res/layout/view_card_footer.xml A app/src/main/res/layout/view_card_header.xml M app/src/main/res/layout/view_list_card.xml M app/src/main/res/values/strings_no_translate.xml 11 files changed, 297 insertions(+), 25 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia refs/changes/54/292054/1 diff --git a/app/src/main/java/org/wikipedia/feed/view/CardFooterView.java b/app/src/main/java/org/wikipedia/feed/view/CardFooterView.java new file mode 100644 index 0000000..ee1b21e --- /dev/null +++ b/app/src/main/java/org/wikipedia/feed/view/CardFooterView.java @@ -0,0 +1,27 @@ +package org.wikipedia.feed.view; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.wikipedia.R; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class CardFooterView extends RelativeLayout { + @BindView(R.id.view_card_footer_text) TextView textView; + + public CardFooterView(Context context) { + super(context); + + inflate(getContext(), R.layout.view_card_footer, this); + ButterKnife.bind(this); + } + + public CardFooterView setText(@Nullable CharSequence text) { + textView.setText(text); + return this; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/wikipedia/feed/view/CardHeaderView.java b/app/src/main/java/org/wikipedia/feed/view/CardHeaderView.java new file mode 100644 index 0000000..539c247 --- /dev/null +++ b/app/src/main/java/org/wikipedia/feed/view/CardHeaderView.java @@ -0,0 +1,44 @@ +package org.wikipedia.feed.view; + +import android.content.Context; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.facebook.drawee.view.SimpleDraweeView; + +import org.wikipedia.R; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class CardHeaderView extends RelativeLayout { + @BindView(R.id.view_card_header_image) SimpleDraweeView imageView; + @BindView(R.id.view_card_header_title) TextView titleView; + @BindView(R.id.view_card_header_subtitle) TextView subtitleView; + + public CardHeaderView(Context context) { + super(context); + + inflate(getContext(), R.layout.view_card_header, this); + ButterKnife.bind(this); + } + + @NonNull + public CardHeaderView setImage(@NonNull Uri uri) { + imageView.setImageURI(uri); + return this; + } + + @NonNull public CardHeaderView setTitle(@Nullable CharSequence title) { + titleView.setText(title); + return this; + } + + @NonNull public CardHeaderView setSubtitle(@Nullable CharSequence subtitle) { + subtitleView.setText(subtitle); + return this; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/wikipedia/feed/view/ListCardView.java b/app/src/main/java/org/wikipedia/feed/view/ListCardView.java index 99b9bd8..2beaecf 100644 --- a/app/src/main/java/org/wikipedia/feed/view/ListCardView.java +++ b/app/src/main/java/org/wikipedia/feed/view/ListCardView.java @@ -5,6 +5,7 @@ import android.support.v7.widget.CardView; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.view.View; import android.view.ViewGroup; import org.wikipedia.R; @@ -12,6 +13,7 @@ import org.wikipedia.views.DefaultViewHolder; import org.wikipedia.views.DrawableItemDecoration; import org.wikipedia.views.MarginItemDecoration; +import org.wikipedia.views.ViewUtil; import java.util.Collections; import java.util.List; @@ -20,6 +22,9 @@ import butterknife.ButterKnife; public class ListCardView extends CardView { + @BindView(R.id.view_list_card_header) View headerView; + @BindView(R.id.view_list_card_footer) View footerView; + @BindView(R.id.view_list_card_list) RecyclerView recyclerView; private RecyclerAdapter recyclerAdapter; @@ -32,12 +37,44 @@ } public void set(@NonNull ListCard card) { + header(card); + footer(card); + recyclerAdapter = new RecyclerAdapter(card.items()); recyclerView.setAdapter(recyclerAdapter); } - public void update() { + public void update(@NonNull ListCard card) { + header(card); + footer(card); + recyclerAdapter.notifyDataSetChanged(); + } + + private void header(@NonNull ListCard card) { + // TODO: [Feed] replace. + CardHeaderView header = new CardHeaderView(getContext()) + .setTitle("Continue reading") + .setSubtitle("2 days ago"); + header(header); + } + + private void header(@NonNull View view) { + ViewUtil.replace(headerView, view); + headerView = view; + } + + private void footer(@NonNull ListCard card) { + // TODO: [Feed] replace. + CardFooterView footer = new CardFooterView(getContext()) + .setText("All top read articles on Fri, April 08"); + footer.setVisibility(card.items().size() > 2 ? VISIBLE : GONE); + footer(footer); + } + + private void footer(@NonNull View view) { + ViewUtil.replace(footerView, view); + footerView = view; } private void initRecycler() { @@ -45,7 +82,7 @@ recyclerView.addItemDecoration(new MarginItemDecoration(getContext(), R.dimen.view_list_card_item_margin)); recyclerView.addItemDecoration(new DrawableItemDecoration(getContext(), - R.drawable.divider)); + R.drawable.divider, true)); recyclerAdapter = new RecyclerAdapter(Collections.<Integer>emptyList()); recyclerView.setAdapter(recyclerAdapter); } diff --git a/app/src/main/java/org/wikipedia/views/DrawableItemDecoration.java b/app/src/main/java/org/wikipedia/views/DrawableItemDecoration.java index 9e3a2be..ffb794d 100644 --- a/app/src/main/java/org/wikipedia/views/DrawableItemDecoration.java +++ b/app/src/main/java/org/wikipedia/views/DrawableItemDecoration.java @@ -2,6 +2,7 @@ import android.content.Context; import android.graphics.Canvas; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; @@ -12,32 +13,73 @@ public class DrawableItemDecoration extends RecyclerView.ItemDecoration { @Nullable private final Drawable drawable; + private final boolean drawEnds; - public DrawableItemDecoration(@NonNull Context context, @DrawableRes int id) { - this(ContextCompat.getDrawable(context, id)); + public DrawableItemDecoration(@NonNull Context context, @DrawableRes int id, + boolean drawEnds) { + this(ContextCompat.getDrawable(context, id), drawEnds); } - public DrawableItemDecoration(@Nullable Drawable drawable) { + public DrawableItemDecoration(@Nullable Drawable drawable, boolean drawEnds) { this.drawable = drawable; + this.drawEnds = drawEnds; } - @Override public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { + @Override public void onDraw(Canvas canvas, @NonNull RecyclerView parent, + RecyclerView.State state) { super.onDraw(canvas, parent, state); - if (drawable == null) { + if (drawable == null || parent.getChildCount() == 0) { return; } - int left = parent.getPaddingLeft(); - int right = parent.getWidth() - parent.getPaddingRight(); - RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); - - for (int i = 0; i < parent.getChildCount(); ++i) { + int end = parent.getChildCount() - 1; + onDrawHeader(canvas, parent, state, parent.getChildAt(0)); + for (int i = 1; i < end; ++i) { View child = parent.getChildAt(i); - int top = layoutManager.getDecoratedBottom(child); - int bottom = top + drawable.getIntrinsicHeight(); - drawable.setBounds(left, top, right, bottom); - drawable.draw(canvas); + onDrawItem(canvas, parent, state, child); + } + onDrawFooter(canvas, parent, state, parent.getChildAt(end)); + } + + private void onDrawHeader(Canvas canvas, @NonNull RecyclerView parent, + RecyclerView.State state, @NonNull View child) { + if (drawEnds) { + draw(canvas, bounds(parent, child, true)); + } + draw(canvas, bounds(parent, child, false)); + } + + private void onDrawFooter(Canvas canvas, @NonNull RecyclerView parent, + RecyclerView.State state, @NonNull View child) { + if (drawEnds) { + draw(canvas, bounds(parent, child, false)); } } -} \ No newline at end of file + + private void onDrawItem(Canvas canvas, @NonNull RecyclerView parent, RecyclerView.State state, + @NonNull View child) { + + draw(canvas, bounds(parent, child, false)); + } + + private Rect bounds(@NonNull RecyclerView parent, @NonNull View child, boolean top) { + RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); + + Rect bounds = new Rect(); + bounds.right = parent.getWidth() - parent.getPaddingRight(); + bounds.left = parent.getPaddingLeft(); + int height = drawable.getIntrinsicHeight(); + bounds.top = top + ? layoutManager.getDecoratedTop(child) + : layoutManager.getDecoratedBottom(child) - height; + bounds.bottom = bounds.top + height; + + return bounds; + } + + private void draw(Canvas canvas, @NonNull Rect bounds) { + drawable.setBounds(bounds); + drawable.draw(canvas); + } +} diff --git a/app/src/main/java/org/wikipedia/views/MarginItemDecoration.java b/app/src/main/java/org/wikipedia/views/MarginItemDecoration.java index 88b18bf..d19927d 100644 --- a/app/src/main/java/org/wikipedia/views/MarginItemDecoration.java +++ b/app/src/main/java/org/wikipedia/views/MarginItemDecoration.java @@ -8,7 +8,7 @@ import android.view.View; public class MarginItemDecoration extends RecyclerView.ItemDecoration { - private final Rect rect = new Rect(); + private final Rect offsets = new Rect(); public MarginItemDecoration(@NonNull Context context, @DimenRes int id) { this(pixelSize(context, id)); @@ -25,13 +25,13 @@ } public MarginItemDecoration(int leftMargin, int topMargin, int rightMargin, int bottomMargin) { - rect.set(leftMargin, topMargin, rightMargin, bottomMargin); + offsets.set(leftMargin, topMargin, rightMargin, bottomMargin); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); - outRect.set(rect); + outRect.set(offsets); } private static int pixelSize(@NonNull Context context, @DimenRes int id) { diff --git a/app/src/main/java/org/wikipedia/views/ViewUtil.java b/app/src/main/java/org/wikipedia/views/ViewUtil.java index ede9913..7c7f5f0 100644 --- a/app/src/main/java/org/wikipedia/views/ViewUtil.java +++ b/app/src/main/java/org/wikipedia/views/ViewUtil.java @@ -8,9 +8,10 @@ import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.view.ActionMode; import android.text.TextUtils; +import android.view.ActionMode; import android.view.View; +import android.view.ViewGroup; import android.view.ViewManager; import android.view.animation.Animation; @@ -42,7 +43,11 @@ return false; } - public static void setBottomPaddingDp(View view, int padding) { + public static void setPadding(@NonNull View view, int padding) { + view.setPadding(padding, padding, padding, padding); + } + + public static void setBottomPaddingDp(@NonNull View view, int padding) { view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), (int) (padding * DimenUtil.getDensityScalar())); } @@ -84,5 +89,32 @@ return returnedBitmap; } + @Nullable public static ViewGroup parent(@NonNull View view) { + return view.getParent() instanceof ViewGroup ? (ViewGroup) view.getParent() : null; + } + + public static void remove(@NonNull View view) { + ViewManager parent = parent(view); + if (parent != null) { + parent.removeView(view); + } + } + + /** Replace the current View with a new View by copying the ID and LayoutParams (by reference). */ + public static void replace(@NonNull View current, @NonNull View next) { + ViewGroup parent = parent(current); + if (parent == null || parent(next) != null) { + String msg = "Parent of current View must be nonnull; parent of next View must be null."; + throw new IllegalStateException(msg); + } + + next.setId(current.getId()); + next.setLayoutParams(current.getLayoutParams()); + + int index = parent.indexOfChild(current); + remove(current); + parent.addView(next, index); + } + private ViewUtil() { } } diff --git a/app/src/main/res/drawable/ic_arrow_forward_black_24dp.xml b/app/src/main/res/drawable/ic_arrow_forward_black_24dp.xml new file mode 100644 index 0000000..0d8872d --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_forward_black_24dp.xml @@ -0,0 +1,5 @@ +<vector android:autoMirrored="true" android:height="24dp" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#f666" android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/> +</vector> diff --git a/app/src/main/res/layout/view_card_footer.xml b/app/src/main/res/layout/view_card_footer.xml new file mode 100644 index 0000000..60d76aa --- /dev/null +++ b/app/src/main/res/layout/view_card_footer.xml @@ -0,0 +1,29 @@ +<merge + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <ImageButton + style="@style/Widget.AppCompat.Toolbar.Button.Navigation" + android:id="@+id/view_card_footer_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + app:srcCompat="@drawable/ic_arrow_forward_black_24dp" + android:contentDescription="@string/view_card_footer_button" /> + + <!-- TODO: [Feed] If making the whole cell clickable, consider using a compound drawable: + 1 Drop the ImageButton. + 2 Set the TextView width to match_parent. + 3 Add android:drawableEnd/Right. --> + <TextView + android:id="@+id/view_card_footer_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_alignRight="@id/view_card_footer_button" + android:layout_alignEnd="@id/view_card_footer_button" + android:textAppearance="@style/TextAppearance.AppCompat.Small" /> + +</merge> \ No newline at end of file diff --git a/app/src/main/res/layout/view_card_header.xml b/app/src/main/res/layout/view_card_header.xml new file mode 100644 index 0000000..8539553 --- /dev/null +++ b/app/src/main/res/layout/view_card_header.xml @@ -0,0 +1,36 @@ +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <com.facebook.drawee.view.SimpleDraweeView + style="@style/SimpleDraweeViewPlaceholder.Article" + android:id="@+id/view_card_header_image" + android:layout_width="@dimen/defaultListItemSize" + android:layout_height="@dimen/defaultListItemSize" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" /> + + <TextView + android:id="@+id/view_card_header_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toRightOf="@id/view_card_header_image" + android:layout_toEndOf="@id/view_card_header_image" + android:layout_marginLeft="24dp" + android:layout_marginRight="24dp" + android:textAppearance="@style/TextAppearance.AppCompat.Medium" + android:textSize="16sp" /> + + <TextView + android:id="@+id/view_card_header_subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toRightOf="@id/view_card_header_image" + android:layout_toEndOf="@id/view_card_header_image" + android:layout_below="@id/view_card_header_title" + android:layout_marginLeft="24dp" + android:layout_marginRight="24dp" + android:textAppearance="@style/TextAppearance.AppCompat.Large" + android:textSize="18sp" + android:textColor="?android:attr/textColorSecondary" + android:textStyle="bold" /> + +</merge> \ No newline at end of file diff --git a/app/src/main/res/layout/view_list_card.xml b/app/src/main/res/layout/view_list_card.xml index 329ebd1..cc1b448 100644 --- a/app/src/main/res/layout/view_list_card.xml +++ b/app/src/main/res/layout/view_list_card.xml @@ -1,8 +1,27 @@ -<merge xmlns:android="http://schemas.android.com/apk/res/android"> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <View + android:id="@+id/view_list_card_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/view_list_card_item_margin" + android:visibility="gone" /> <android.support.v7.widget.RecyclerView android:id="@+id/view_list_card_list" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:layout_below="@id/view_list_card_header" /> -</merge> \ No newline at end of file + <View + android:id="@+id/view_list_card_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/view_list_card_list" + android:layout_margin="@dimen/view_list_card_item_margin" + android:visibility="gone" /> + +</RelativeLayout> \ No newline at end of file diff --git a/app/src/main/res/values/strings_no_translate.xml b/app/src/main/res/values/strings_no_translate.xml index 9822eb0..4d51ced 100644 --- a/app/src/main/res/values/strings_no_translate.xml +++ b/app/src/main/res/values/strings_no_translate.xml @@ -45,5 +45,6 @@ <string name="feed">Home</string> <string name="activity_feed_title">@string/feed</string> <string name="nav_item_feed">@string/feed</string> + <string name="view_card_footer_button">See more</string> <!-- /The Feed --> </resources> -- To view, visit https://gerrit.wikimedia.org/r/292054 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I53bbf5dc70223c2775e83f38df49cce6480c7700 Gerrit-PatchSet: 1 Gerrit-Project: apps/android/wikipedia Gerrit-Branch: master Gerrit-Owner: Niedzielski <sniedziel...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits