Java tutorial
/* * Copyright (c) 2015 Jonas Kalderstam. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.nononsenseapps.feeder.ui; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.animation.TypeEvaluator; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; import android.widget.TextView; import com.nononsenseapps.feeder.R; import com.nononsenseapps.feeder.db.FeedSQL; import com.nononsenseapps.feeder.db.RssContentProvider; import com.nononsenseapps.feeder.model.ExpandableSortedList; import com.nononsenseapps.feeder.model.RssNotifications; import com.nononsenseapps.feeder.util.FeedDeltaCursorLoader; import com.nononsenseapps.feeder.util.LPreviewUtils; import com.nononsenseapps.feeder.util.LPreviewUtilsBase; import com.nononsenseapps.feeder.util.PrefUtils; import com.nononsenseapps.feeder.views.ObservableScrollView; import java.text.Collator; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; /** * Base activity which handles navigation drawer and other bloat common * between activities. */ public class BaseActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks { public static final String SHOULD_FINISH_BACK = "SHOULD_FINISH_BACK"; // Durations for certain animations we use: public static final int HEADER_HIDE_ANIM_DURATION = 300; // Special Navdrawer items protected static final int NAVDRAWER_ITEM_INVALID = -1; // delay to launch nav drawer item, to allow close animation to play private static final int NAVDRAWER_LAUNCH_DELAY = 250; // fade in and fade out durations for the main content when switching between // different Activities of the app through the Nav Drawer private static final int MAIN_CONTENT_FADEOUT_DURATION = 150; private static final int MAIN_CONTENT_FADEIN_DURATION = 250; private static final TypeEvaluator ARGB_EVALUATOR = new ArgbEvaluator(); // Positive numbers reserved for children private static final int NAV_TAGS_LOADER = -2; protected boolean mActionBarShown = true; // If pressing home should finish or start new activity protected boolean mShouldFinishBack = false; protected Toolbar mActionBarToolbar; //protected MultiScrollListener mMultiScrollListener; private ObjectAnimator mStatusBarColorAnimator; // When set, these components will be shown/hidden in sync with the action bar // to implement the "quick recall" effect (the Action Bar and the header views disappear // when you scroll down a list, and reappear quickly when you scroll up). private ArrayList<View> mHideableHeaderViews = new ArrayList<View>(); private ArrayList<View> mHideableFooterViews = new ArrayList<View>(); // variables that control the Action Bar auto hide behavior (aka "quick recall") private boolean mActionBarAutoHideEnabled = false; private int mActionBarAutoHideSensivity = 0; private int mActionBarAutoHideMinY = 0; private int mActionBarAutoHideSignal = 0; private int mThemedStatusBarColor; private LPreviewUtilsBase mLPreviewUtils; private DrawerLayout mDrawerLayout; private LPreviewUtilsBase.ActionBarDrawerToggleWrapper mDrawerToggle; // A Runnable that we should execute when the navigation drawer finishes its closing animation private Runnable mDeferredOnDrawerClosedRunnable; private FeedsAdapter mNavAdapter; /** * Converts an intent into a {@link Bundle} suitable for use as fragment * arguments. */ public static Bundle intentToFragmentArguments(Intent intent) { Bundle arguments = new Bundle(); if (intent == null) { return arguments; } final Uri data = intent.getData(); if (data != null) { arguments.putParcelable("_uri", data); } final Bundle extras = intent.getExtras(); if (extras != null) { arguments.putAll(intent.getExtras()); } return arguments; } /** * Converts a fragment arguments bundle into an intent. */ public static Intent fragmentArgumentsToIntent(Bundle arguments) { Intent intent = new Intent(); if (arguments == null) { return intent; } final Uri data = arguments.getParcelable("_uri"); if (data != null) { intent.setData(data); } intent.putExtras(arguments); intent.removeExtra("_uri"); return intent; } /** * Set the background depending on user preferences */ protected void setNightBackground() { // Change background TypedValue typedValue = new TypedValue(); if (PrefUtils.isNightMode(this)) { // Get black getTheme().resolveAttribute(R.attr.nightBGColor, typedValue, true); } else { getTheme().resolveAttribute(android.R.attr.windowBackground, typedValue, true); } getWindow().setBackgroundDrawable(new ColorDrawable(typedValue.data)); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent i = getIntent(); if (i != null) { mShouldFinishBack = i.getBooleanExtra(SHOULD_FINISH_BACK, false); } mLPreviewUtils = LPreviewUtils.getInstance(this); mThemedStatusBarColor = getResources().getColor(R.color.primary_dark); setNightBackground(); // Add account and enable sync - if not done before RssContentProvider.SetupSync(this); } @Override public void onResume() { super.onResume(); // Send notifications for configured feeds RssNotifications.notify(this); } protected Toolbar getActionBarToolbar() { if (mActionBarToolbar == null) { mActionBarToolbar = (Toolbar) findViewById(R.id.toolbar_actionbar); if (mActionBarToolbar != null) { setSupportActionBar(mActionBarToolbar); } } return mActionBarToolbar; } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); setupNavDrawer(); // View mainContent = findViewById(R.id.main_content); // if (mainContent != null) { // mainContent.setAlpha(0); // mainContent.animate().alpha(1) // .setDuration(MAIN_CONTENT_FADEIN_DURATION); // } } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) { return true; } switch (id) { case android.R.id.home: if (mShouldFinishBack) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { finishAfterTransition(); } else { finish(); } } else { Intent intent = new Intent(this, FeedActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); finish(); } return true; default: return super.onOptionsItemSelected(item); } } private void setupNavDrawer() { // Show icon ActionBar ab = getSupportActionBar(); if (ab != null) { ab.setDisplayHomeAsUpEnabled(true); ab.setHomeButtonEnabled(true); } // What nav drawer item should be selected? int selfItem = getSelfNavDrawerItem(); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); if (mDrawerLayout == null) { return; } if (selfItem == NAVDRAWER_ITEM_INVALID) { // do not show a nav drawer View navDrawer = mDrawerLayout.findViewById(R.id.navdrawer); if (navDrawer != null) { ((ViewGroup) navDrawer.getParent()).removeView(navDrawer); } mDrawerLayout = null; return; } mDrawerToggle = mLPreviewUtils.setupDrawerToggle(mDrawerLayout, new DrawerLayout.DrawerListener() { @Override public void onDrawerClosed(View drawerView) { // run deferred action, if we have one if (mDeferredOnDrawerClosedRunnable != null) { mDeferredOnDrawerClosedRunnable.run(); mDeferredOnDrawerClosedRunnable = null; } invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() updateStatusBarForNavDrawerSlide(0f); onNavDrawerStateChanged(false, false); } @Override public void onDrawerOpened(View drawerView) { invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() updateStatusBarForNavDrawerSlide(1f); onNavDrawerStateChanged(true, false); } @Override public void onDrawerStateChanged(int newState) { invalidateOptionsMenu(); onNavDrawerStateChanged(isNavDrawerOpen(), newState != DrawerLayout.STATE_IDLE); } @Override public void onDrawerSlide(View drawerView, float slideOffset) { updateStatusBarForNavDrawerSlide(slideOffset); onNavDrawerSlide(slideOffset); } }); mDrawerToggle.syncState(); // Recycler view stuff RecyclerView mRecyclerView = (RecyclerView) mDrawerLayout.findViewById(R.id.navdrawer_list); mRecyclerView.setHasFixedSize(true); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mNavAdapter = new FeedsAdapter(); mRecyclerView.setAdapter(mNavAdapter); populateNavDrawer(); // When the user runs the app for the first time, we want to land them with the // navigation drawer open. But just the first time. if (!PrefUtils.isWelcomeDone(this)) { // first run of the app starts with the nav drawer open PrefUtils.markWelcomeDone(this); mDrawerLayout.openDrawer(GravityCompat.START); } } /** * Open the nav drawer */ public void openNavDrawer() { mDrawerLayout.openDrawer(GravityCompat.START); } /** * Returns the navigation drawer item that corresponds to this Activity. * Subclasses * of BaseActivity override this to indicate what nav drawer item * corresponds to them * Return NAVDRAWER_ITEM_INVALID to mean that this Activity should not have * a Nav Drawer. */ protected int getSelfNavDrawerItem() { return NAVDRAWER_ITEM_INVALID; } private void updateStatusBarForNavDrawerSlide(float slideOffset) { if (mStatusBarColorAnimator != null) { mStatusBarColorAnimator.cancel(); } if (!mActionBarShown) { mLPreviewUtils.setStatusBarColor(Color.BLACK); return; } mLPreviewUtils.setStatusBarColor( (Integer) ARGB_EVALUATOR.evaluate(slideOffset, mThemedStatusBarColor, Color.BLACK)); } // Subclasses can override this for custom behavior protected void onNavDrawerStateChanged(boolean isOpen, boolean isAnimating) { if (mActionBarAutoHideEnabled && isOpen) { autoShowOrHideActionBar(true); } } protected boolean isNavDrawerOpen() { return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START); } protected void onNavDrawerSlide(float offset) { } // Subclasses can override to decide what happens on nav item selection protected void onNavigationDrawerItemSelected(long id, String title, String url, String tag) { // TODO add default start activity with arguments } private void populateNavDrawer() { getSupportLoaderManager().restartLoader(NAV_TAGS_LOADER, new Bundle(), this); } public void showActionBar() { autoShowOrHideActionBar(true); } protected void autoShowOrHideActionBar(boolean show) { if (show == mActionBarShown) { return; } mActionBarShown = show; onActionBarAutoShowOrHide(show); } protected void onActionBarAutoShowOrHide(boolean shown) { if (mStatusBarColorAnimator != null) { mStatusBarColorAnimator.cancel(); } mStatusBarColorAnimator = ObjectAnimator .ofInt(mLPreviewUtils, "statusBarColor", shown ? mThemedStatusBarColor : Color.BLACK) .setDuration(250); mStatusBarColorAnimator.setEvaluator(ARGB_EVALUATOR); mStatusBarColorAnimator.start(); for (View view : mHideableHeaderViews) { if (shown) { view.animate().translationY(0).alpha(1).setDuration(HEADER_HIDE_ANIM_DURATION) .setInterpolator(new DecelerateInterpolator()); } else { view.animate().translationY(-view.getBottom()).alpha(0).setDuration(HEADER_HIDE_ANIM_DURATION) .setInterpolator(new DecelerateInterpolator()); } } for (View view : mHideableFooterViews) { if (shown) { view.animate().translationY(0).alpha(1).setDuration(HEADER_HIDE_ANIM_DURATION) .setInterpolator(new DecelerateInterpolator()); } else { view.animate().translationY(view.getHeight()).alpha(0).setDuration(HEADER_HIDE_ANIM_DURATION) .setInterpolator(new DecelerateInterpolator()); } } } protected void enableActionBarAutoHide(final RecyclerView listView) { initActionBarAutoHide(); final LinearLayoutManager layoutManager = (LinearLayoutManager) listView.getLayoutManager(); mActionBarAutoHideSignal = 0; listView.addOnScrollListener(new RecyclerView.OnScrollListener() { final static int ITEMS_THRESHOLD = 0; int lastFvi = 0; @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); boolean force = false; int firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); if (recyclerView.getAdapter() != null) { int lastPos = recyclerView.getAdapter().getItemCount() - 1; if (layoutManager.findLastVisibleItemPosition() == lastPos) { // Show when last item is visible force = true; } } onMainContentScrolled(firstVisibleItem <= ITEMS_THRESHOLD ? 0 : Integer.MAX_VALUE, lastFvi - firstVisibleItem > 0 ? Integer.MIN_VALUE : lastFvi == firstVisibleItem ? 0 : Integer.MAX_VALUE, force); lastFvi = firstVisibleItem; } }); } /** * Initializes the Action Bar auto-hide (aka Quick Recall) effect. */ private void initActionBarAutoHide() { mActionBarAutoHideEnabled = true; mActionBarAutoHideMinY = getResources().getDimensionPixelSize(R.dimen.action_bar_auto_hide_min_y); mActionBarAutoHideSensivity = getResources().getDimensionPixelSize(R.dimen.action_bar_auto_hide_sensivity); } /** * Indicates that the main content has scrolled (for the purposes of * showing/hiding * the action bar for the "action bar auto hide" effect). currentY and * deltaY may be exact * (if the underlying view supports it) or may be approximate indications: * deltaY may be INT_MAX to mean "scrolled forward indeterminately" and * INT_MIN to mean * "scrolled backward indeterminately". currentY may be 0 to mean * "somewhere close to the * start of the list" and INT_MAX to mean "we don't know, but not at the * start of the list" */ private void onMainContentScrolled(int currentY, int deltaY, boolean force) { if (deltaY > mActionBarAutoHideSensivity) { deltaY = mActionBarAutoHideSensivity; } else if (deltaY < -mActionBarAutoHideSensivity) { deltaY = -mActionBarAutoHideSensivity; } if (Math.signum(deltaY) * Math.signum(mActionBarAutoHideSignal) < 0) { // deltaY is a motion opposite to the accumulated signal, so reset signal mActionBarAutoHideSignal = deltaY; } else { // add to accumulated signal mActionBarAutoHideSignal += deltaY; } boolean shouldShow = currentY < mActionBarAutoHideMinY || (mActionBarAutoHideSignal <= -mActionBarAutoHideSensivity); autoShowOrHideActionBar(shouldShow | force); } protected void enableActionBarAutoHide(final ObservableScrollView scrollView) { initActionBarAutoHide(); mActionBarAutoHideSignal = 0; scrollView.addOnScrollChangedListener(new ObservableScrollView.OnScrollChangedListener() { @Override public void onScrollChanged(final int deltaX, final int deltaY) { onMainContentScrolled(scrollView.getScrollY(), deltaY, false); } }); } protected void registerHideableHeaderView(View hideableHeaderView) { if (!mHideableHeaderViews.contains(hideableHeaderView)) { mHideableHeaderViews.add(hideableHeaderView); } } protected void deregisterHideableHeaderView(View hideableHeaderView) { if (mHideableHeaderViews.contains(hideableHeaderView)) { mHideableHeaderViews.remove(hideableHeaderView); } } protected void registerHideableFooterView(View hideableFooterView) { if (!mHideableFooterViews.contains(hideableFooterView)) { mHideableFooterViews.add(hideableFooterView); } } protected void deregisterHideableFooterView(View hideableFooterView) { if (mHideableFooterViews.contains(hideableFooterView)) { mHideableFooterViews.remove(hideableFooterView); } } @Override public Loader onCreateLoader(final int id, final Bundle bundle) { return new FeedDeltaCursorLoader(this, FeedSQL.URI_FEEDSWITHCOUNTS, FeedSQL.FIELDS_VIEWCOUNT, null, null, null); } @Override public void onLoadFinished(final Loader Loader, final Object obj) { mNavAdapter.updateData((HashMap<FeedSQL, Integer>) obj); } @Override public void onLoaderReset(final Loader loader) { // .. } static class FeedWrapper { public final String tag; public final FeedSQL item; public final boolean isTag; public FeedWrapper(String tag) { this.tag = tag; item = null; isTag = true; } public FeedWrapper(FeedSQL item) { tag = item.tag; this.item = item; isTag = false; } @Override public boolean equals(Object o) { if (o == null) { return false; } else if (o instanceof FeedWrapper) { FeedWrapper f = (FeedWrapper) o; if (isTag && f.isTag) { // Compare tags return tag.equals(f.tag); } else { // Compare items return !isTag && !f.isTag && item.equals(f.item); } } else { return false; } } @Override public int hashCode() { if (isTag) { // Tag return tag.hashCode(); } else { // Item return item.hashCode(); } } @Override public String toString() { if (isTag) { return "Tag: " + tag; } else { return "Item: " + item.customTitle + " (" + tag + ")"; } } } class FeedsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int VIEWTYPE_TAG = 0; private static final int VIEWTYPE_FEED = 1; private static final int VIEWTYPE_FEED_CHILD = 2; private final String TAG = FeedsAdapter.class.getSimpleName(); private final ExpandableSortedList<FeedWrapper> mItems; private final Collator mCollator; private HashMap<Long, FeedWrapper> mItemMap; public FeedsAdapter() { super(); mCollator = Collator.getInstance(); setHasStableIds(true); mItemMap = new HashMap<>(); mItems = new ExpandableSortedList<>(FeedWrapper.class, new ExpandableSortedList.ExpandableCallback<FeedWrapper>() { @Override public int getItemLevel(FeedWrapper item) { if (item.isTag || item.item.tag == null || item.item.tag.isEmpty()) { return ExpandableSortedList.TOP_LEVEL; } else { return ExpandableSortedList.TOP_LEVEL + 1; } } // Null safe private boolean sameParent(FeedWrapper o1, FeedWrapper o2) { try { //noinspection ConstantConditions return getParentOf(o1).equals(getParentOf(o2)); } catch (NullPointerException e) { return getParentOf(o1) == null && getParentOf(o2) == null; } } @Override public FeedWrapper getParentOf(FeedWrapper item) { if (item.isTag || item.item.tag == null || item.item.tag.isEmpty()) { return null; } else { return new FeedWrapper(item.tag); } } @Override public int getParentUnreadCount(FeedWrapper parent, HashSet<FeedWrapper> children) { if (children == null) { return 0; } int result = 0; for (FeedWrapper wrap : children) { result += wrap.item.unreadCount; } return result; } @Override public int compare(FeedWrapper o1, FeedWrapper o2) { // Compare only on the same level if (getItemLevel(o1) != getItemLevel(o2)) { int result; if (getItemLevel(o1) < getItemLevel(o2)) { result = compare(o1, getParentOf(o2)); if (result == 0) { // Was equal to parent, which should come first // Can only happen if both are my parent result = -1; } } else { result = compare(getParentOf(o1), o2); if (result == 0) { // Was equal to parent, which should come first // Can only happen if both are my parent result = 1; } } return result; } // Only compare with same parent else if (!sameParent(o1, o2)) { // Same level, so has to be a sublevel where parents exist // But just to be safe, catch any possible exceptions try { return compare(getParentOf(o1), getParentOf(o2)); } catch (NullPointerException e) { return 0; } } // Same level guaranteed now, with same parent else if (o1.isTag != o2.isTag) { // Tags always win if (o1.isTag) { return -1; } else { return 1; } } // Both tags, or both not else if (o1.isTag) { return mCollator.compare(o1.tag, o2.tag); } // Both items here with same parent else { return mCollator.compare(o1.item.customTitle, o2.item.customTitle); } } @Override public void onInserted(int position, int count) { notifyItemRangeInserted(position, count); } @Override public void onRemoved(int position, int count) { notifyItemRangeRemoved(position, count); } @Override public void onMoved(int fromPosition, int toPosition) { notifyItemMoved(fromPosition, toPosition); } @Override public void onChanged(int position, int count) { notifyItemRangeChanged(position, count); } @Override public boolean areContentsTheSame(FeedWrapper oldItem, FeedWrapper newItem) { if (oldItem.isTag && newItem.isTag) { return oldItem.tag.equals(newItem.tag) && mItems.getTagUnreadCount(oldItem) == mItems.getTagUnreadCount(newItem); } else if (!oldItem.isTag && !newItem.isTag) { return oldItem.item.customTitle.equals(newItem.item.customTitle) && oldItem.item.unreadCount == newItem.item.unreadCount; } else { return false; } } @Override public boolean areItemsTheSame(FeedWrapper item1, FeedWrapper item2) { return item1.equals(item2); } }); } @Override public int getItemCount() { return mItems.size(); } @Override public long getItemId(final int position) { FeedWrapper item = mItems.get(position); if (item.isTag) { return item.hashCode(); } else { return item.item.id; } } @Override public int getItemViewType(int position) { FeedWrapper item = mItems.get(position); if (item.isTag) { return VIEWTYPE_TAG; } else if (item.tag == null || item.tag.isEmpty()) { return VIEWTYPE_FEED; } else { return VIEWTYPE_FEED_CHILD; } } public void updateData(HashMap<FeedSQL, Integer> map) { HashMap<Long, FeedWrapper> oldItemMap = mItemMap; mItemMap = new HashMap<>(); mItems.beginBatchedUpdates(); for (FeedSQL item : map.keySet()) { FeedWrapper wrap = new FeedWrapper(item); if (map.get(item) >= 0) { if (map.get(item) == 0) { // First remove it, tree structure needs to be updated FeedWrapper oldWrap = oldItemMap.remove(item.id); mItems.remove(oldWrap); } mItems.add(wrap); // Add to new map as well mItemMap.put(item.id, wrap); } else { mItems.remove(wrap); // And remove from old oldItemMap.remove(item.id); } } // If any items remain in old set, they are not present in current result set, // remove them. This is pretty much what is done in the delta loader, but if // the loader is restarted, then it has no old data to go on. for (FeedWrapper item : oldItemMap.values()) { mItems.remove(item); } mItems.endBatchedUpdates(); } /** * @param tag * @return true if tag is expanded after this call, false otherwise */ public boolean toggleExpansion(FeedWrapper tag) { return mItems.toggleExpansion(tag); } public boolean isExpanded(FeedWrapper tag) { return mItems.isExpanded(tag); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case VIEWTYPE_FEED: return new FeedHolder( LayoutInflater.from(BaseActivity.this).inflate(R.layout.view_feed, parent, false)); case VIEWTYPE_FEED_CHILD: return new FeedHolder( LayoutInflater.from(BaseActivity.this).inflate(R.layout.view_feed_child, parent, false)); case VIEWTYPE_TAG: return new TagHolder( LayoutInflater.from(BaseActivity.this).inflate(R.layout.view_feed_tag, parent, false)); default: return null; } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { FeedWrapper wrap = mItems.get(position); switch (getItemViewType(position)) { case VIEWTYPE_FEED: case VIEWTYPE_FEED_CHILD: FeedHolder fh = (FeedHolder) holder; fh.item = wrap.item; fh.title.setText(fh.item.customTitle); fh.unreadCount.setText(Integer.toString(fh.item.unreadCount)); fh.unreadCount.setVisibility(fh.item.unreadCount > 0 ? View.VISIBLE : View.INVISIBLE); break; case VIEWTYPE_TAG: TagHolder th = (TagHolder) holder; th.wrap = wrap; th.title.setText(wrap.tag); if (isExpanded(wrap)) { th.expander.setImageResource(R.drawable.tinted_expand_less); } else { th.expander.setImageResource(R.drawable.tinted_expand_more); } int uc = mItems.getTagUnreadCount(wrap); th.unreadCount.setText(Integer.toString(uc)); th.unreadCount.setVisibility(uc > 0 ? View.VISIBLE : View.INVISIBLE); break; } } } public class TagHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private final TextView title; private final TextView unreadCount; private final ImageView expander; private FeedWrapper wrap; public TagHolder(View v) { super(v); title = (TextView) v.findViewById(R.id.tag_name); unreadCount = (TextView) v.findViewById(R.id.tag_unreadcount); expander = (ImageView) v.findViewById(R.id.tag_expander); // expander clicker expander.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mNavAdapter.toggleExpansion(wrap)) { expander.setImageResource(R.drawable.tinted_expand_less); } else { expander.setImageResource(R.drawable.tinted_expand_more); } } }); v.setOnClickListener(this); } /** * Called when a view has been clicked. * * @param v The view that was clicked. */ @Override public void onClick(View v) { if (mDrawerLayout != null) { mDrawerLayout.closeDrawers();//GravityCompat.START); } onNavigationDrawerItemSelected(-1, wrap.tag, null, wrap.tag); } } public class FeedHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private final TextView unreadCount; private final TextView title; public FeedSQL item = null; public FeedHolder(View v) { super(v); unreadCount = (TextView) v.findViewById(R.id.feed_unreadcount); title = (TextView) v.findViewById(R.id.feed_name); v.setOnClickListener(this); } /** * Called when a view has been clicked. * * @param v The view that was clicked. */ @Override public void onClick(View v) { if (mDrawerLayout != null) { mDrawerLayout.closeDrawer(GravityCompat.START); } onNavigationDrawerItemSelected(item.id, item.title, item.url, item.tag); } } }