From 7783ff3b42551dd54d2e12667a48cc633b4acc20 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Fri, 24 Oct 2014 13:21:15 +0400 Subject: [PATCH] initial implementation of recyclerview --- org.fox.ttrss/build.gradle | 10 +- org.fox.ttrss/org.fox.ttrss.iml | 9 +- org.fox.ttrss/src/main/AndroidManifest.xml | 10 + .../java/org/fox/ttrss/HeadlinesFragment.java | 291 +++++++++++------- .../java/org/fox/ttrss/OnlineActivity.java | 8 +- .../fox/ttrss/util/RecyclerArrayAdapter.java | 94 ++++++ .../main/res/layout/headlines_fragment.xml | 6 +- 7 files changed, 305 insertions(+), 123 deletions(-) create mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/util/RecyclerArrayAdapter.java diff --git a/org.fox.ttrss/build.gradle b/org.fox.ttrss/build.gradle index d5c23ef9..a8c99c78 100644 --- a/org.fox.ttrss/build.gradle +++ b/org.fox.ttrss/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 19 + compileSdkVersion 21 buildToolsVersion "20.0.0" defaultConfig { applicationId "org.fox.ttrss" - minSdkVersion 8 + minSdkVersion 9 targetSdkVersion 19 } @@ -24,12 +24,14 @@ android { dependencies { compile project(':taskerlocaleapi') + compile 'com.android.support:cardview-v7:21.0.0' + compile 'com.android.support:recyclerview-v7:21.0.0' compile 'com.jeremyfeinstein.slidingmenu:library:1.3@aar' compile 'com.readystatesoftware.systembartint:systembartint:1.0.3' compile 'com.viewpagerindicator:library:2.4.1' - compile 'com.android.support:support-v4:19.1.0' + compile 'com.android.support:support-v4:19.0.0' + compile 'com.android.support:appcompat-v7:19.0.0' compile 'com.google.code.gson:gson:1.7.1' - compile 'com.android.support:appcompat-v7:19.1.0' compile files('libs/dashclock-api-r1.1.jar') compile files('libs/jsoup-1.6.1.jar') compile files('libs/universal-image-loader-1.9.3.jar') diff --git a/org.fox.ttrss/org.fox.ttrss.iml b/org.fox.ttrss/org.fox.ttrss.iml index ea2bf186..7f3fbd73 100644 --- a/org.fox.ttrss/org.fox.ttrss.iml +++ b/org.fox.ttrss/org.fox.ttrss.iml @@ -78,13 +78,16 @@ - + + + + + + - - diff --git a/org.fox.ttrss/src/main/AndroidManifest.xml b/org.fox.ttrss/src/main/AndroidManifest.xml index 939326af..dfa5f247 100644 --- a/org.fox.ttrss/src/main/AndroidManifest.xml +++ b/org.fox.ttrss/src/main/AndroidManifest.xml @@ -37,6 +37,8 @@ android:name=".FeedsActivity" android:label="@string/app_name" android:uiOptions="splitActionBarWhenNarrow" > + @@ -45,6 +47,8 @@ android:name=".HeadlinesActivity" android:label="@string/app_name" android:uiOptions="splitActionBarWhenNarrow" > + @@ -68,11 +72,17 @@ android:name=".offline.OfflineFeedsActivity" android:label="@string/app_name" android:uiOptions="splitActionBarWhenNarrow" > + + + + )m_articles); - - /* if (!m_activity.isCompatMode()) { - AnimationSet set = new AnimationSet(true); - - Animation animation = new AlphaAnimation(0.0f, 1.0f); - animation.setDuration(500); - set.addAnimation(animation); - - animation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 50.0f,Animation.RELATIVE_TO_SELF, 0.0f, - Animation.RELATIVE_TO_SELF, 0.0f,Animation.RELATIVE_TO_SELF, 0.0f - ); - animation.setDuration(1000); - set.addAnimation(animation); - - LayoutAnimationController controller = new LayoutAnimationController(set, 0.5f); - - list.setLayoutAnimation(controller); - } */ - + final RecyclerView list = (RecyclerView)view.findViewById(R.id.headlines); + + m_adapter = new ArticleListAdapter(m_articles); + + list.setLayoutManager(new LinearLayoutManager(getActivity())); list.setAdapter(m_adapter); - list.setOnItemClickListener(this); - list.setOnScrollListener(this); - //list.setEmptyView(view.findViewById(R.id.no_headlines)); registerForContextMenu(list); - + + list.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + + if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE && m_prefs.getBoolean("headlines_mark_read_scroll", false)) { + Log.d(TAG, "scroll ended!"); + + if (!m_readArticles.isEmpty()) { + m_activity.toggleArticlesUnread(m_readArticles); + m_activity.refresh(false); + m_readArticles.clear(); + } + } + + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + + LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); + + int firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); + int visibleItemCount = layoutManager.findLastCompletelyVisibleItemPosition() - layoutManager.findFirstVisibleItemPosition() + 1; + + Log.d(TAG, "fvI= " + firstVisibleItem + " vIC=" + visibleItemCount); + + if (!m_refreshInProgress && m_articles.findById(-1) != null && firstVisibleItem + visibleItemCount == m_articles.size()) { + refresh(true); + } + + if (m_prefs.getBoolean("headlines_mark_read_scroll", false) && firstVisibleItem > 0 && !m_autoCatchupDisabled) { + Article a = m_articles.get(firstVisibleItem - 1); + + if (a != null && a.unread) { + a.unread = false; + m_readArticles.add(a); + m_feed.unread--; + } + } + + } + }); + //m_activity.m_pullToRefreshAttacher.addRefreshableView(list, this); //if (m_activity.isSmallScreen()) @@ -385,7 +413,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, m_listener = (HeadlinesEventListener) activity; } - @Override + /* @Override public void onItemClick(AdapterView av, View view, int position, long id) { ListView list = (ListView)av; @@ -404,7 +432,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, m_adapter.notifyDataSetChanged(); } } - } + } */ public void refresh(boolean append) { refresh(append, false); @@ -426,11 +454,11 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, if (!append) { if (getView() != null) { Log.d(TAG, "scroll hack"); - ListView list = (ListView)getView().findViewById(R.id.headlines); + RecyclerView list = (RecyclerView) getView().findViewById(R.id.headlines); m_autoCatchupDisabled = true; - list.setSelection(0); + //list.setSelection(0); m_autoCatchupDisabled = false; - list.setEmptyView(null); + //list.setEmptyView(null); m_adapter.clear(); m_adapter.notifyDataSetChanged(); } @@ -450,13 +478,13 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, protected void onPostExecute(JsonElement result) { if (isDetached()) return; - if (getView() != null) { + /* if (getView() != null) { ListView list = (ListView)getView().findViewById(R.id.headlines); if (list != null) { list.setEmptyView(getView().findViewById(R.id.no_headlines)); } - } + } */ m_activity.setProgressBarVisibility(false); @@ -651,23 +679,8 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, } } */ - static class HeadlineViewHolder { - public TextView titleView; - public TextView feedTitleView; - public ImageView markedView; - public ImageView publishedView; - public TextView excerptView; - public ImageView flavorImageView; - public TextView authorView; - public TextView dateView; - public CheckBox selectionBoxView; - public ImageView menuButtonView; - public ViewGroup flavorImageHolder; - - } - - private class ArticleListAdapter extends ArrayAdapter
{ - private ArrayList
items; + private class ArticleListAdapter extends RecyclerArrayAdapter { + //private ArrayList
items; public static final int VIEW_NORMAL = 0; public static final int VIEW_UNREAD = 1; @@ -680,7 +693,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, private final Integer[] origTitleColors = new Integer[VIEW_COUNT]; private final int titleHighScoreUnreadColor; - public ArticleListAdapter(Context context, int textViewResourceId, ArrayList
items) { + /* public ArticleListAdapter(Context context, int textViewResourceId, ArrayList
items) { super(context, textViewResourceId, items); this.items = items; @@ -688,15 +701,106 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, TypedValue tv = new TypedValue(); theme.resolveAttribute(R.attr.headlineTitleHighScoreUnreadTextColor, tv, true); titleHighScoreUnreadColor = tv.data; - } - + } */ + + public class HeadlineViewHolder extends RecyclerView.ViewHolder { + public TextView titleView; + public TextView feedTitleView; + public ImageView markedView; + public ImageView publishedView; + public TextView excerptView; + public ImageView flavorImageView; + public TextView authorView; + public TextView dateView; + public CheckBox selectionBoxView; + public ImageView menuButtonView; + public ViewGroup flavorImageHolder; + public View headlineView; + + public HeadlineViewHolder(View v) { + super(v); + + titleView = (TextView)v.findViewById(R.id.title); + feedTitleView = (TextView)v.findViewById(R.id.feed_title); + markedView = (ImageView)v.findViewById(R.id.marked); + publishedView = (ImageView)v.findViewById(R.id.published); + excerptView = (TextView)v.findViewById(R.id.excerpt); + flavorImageView = (ImageView) v.findViewById(R.id.flavor_image); + authorView = (TextView)v.findViewById(R.id.author); + dateView = (TextView) v.findViewById(R.id.date); + selectionBoxView = (CheckBox) v.findViewById(R.id.selected); + menuButtonView = (ImageView) v.findViewById(R.id.article_menu_button); + flavorImageHolder = (ViewGroup) v.findViewById(R.id.flavorImageHolder); + headlineView = v; + } + } + + + // Create new views (invoked by the layout manager) + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, + int viewType) { + /* // create a new view + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.my_text_view, parent, false); + // set the view's size, margins, paddings and layout parameters + ... + ViewHolder vh = new ViewHolder(v); + return vh; */ + + int layoutId = R.layout.headlines_row; + + switch (viewType) { + case VIEW_LOADMORE: + layoutId = R.layout.headlines_row_loadmore; + break; + case VIEW_UNREAD: + layoutId = R.layout.headlines_row_unread; + break; + case VIEW_SELECTED: + layoutId = R.layout.headlines_row_selected; + break; + case VIEW_SELECTED_UNREAD: + layoutId = R.layout.headlines_row_selected_unread; + break; + } + + View v = LayoutInflater.from(parent.getContext()) + .inflate(layoutId, parent, false); + + ((ViewGroup)v).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + + HeadlineViewHolder vh = new HeadlineViewHolder(v); + + return vh; + } + + /* @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + HeadlineViewHolder avh = (HeadlineViewHolder) holder; + + // - get element from your dataset at this position + // - replace the contents of the view with that element + //holder.mTextView.setText(mDataset[position]); + + } */ + + public ArticleListAdapter(ArrayList
items) { + super(items); + + Theme theme = getActivity().getTheme(); + TypedValue tv = new TypedValue(); + theme.resolveAttribute(R.attr.headlineTitleHighScoreUnreadTextColor, tv, true); + titleHighScoreUnreadColor = tv.data; + } + public int getViewTypeCount() { return VIEW_COUNT; } @Override public int getItemViewType(int position) { - Article a = items.get(position); + Article a = m_items.get(position); if (a.id == -1) { return VIEW_LOADMORE; @@ -711,61 +815,32 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, } } - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View v = convertView; - - final Article article = items.get(position); - HeadlineViewHolder holder; - + @Override + public void onBindViewHolder(RecyclerView.ViewHolder h, final int position) { + HeadlineViewHolder holder = (HeadlineViewHolder) h; + final Article article = m_items.get(position); + int headlineFontSize = Integer.parseInt(m_prefs.getString("headlines_font_size_sp", "13")); int headlineSmallFontSize = Math.max(10, Math.min(18, headlineFontSize - 2)); - - if (v == null) { - int layoutId = R.layout.headlines_row; - - switch (getItemViewType(position)) { - case VIEW_LOADMORE: - layoutId = R.layout.headlines_row_loadmore; - break; - case VIEW_UNREAD: - layoutId = R.layout.headlines_row_unread; - break; - case VIEW_SELECTED: - layoutId = R.layout.headlines_row_selected; - break; - case VIEW_SELECTED_UNREAD: - layoutId = R.layout.headlines_row_selected_unread; - break; - } - - LayoutInflater vi = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - v = vi.inflate(layoutId, null); - holder = new HeadlineViewHolder(); - holder.titleView = (TextView)v.findViewById(R.id.title); + holder.headlineView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (article.id >= 0) { + m_listener.onArticleSelected(article); - holder.feedTitleView = (TextView)v.findViewById(R.id.feed_title); - holder.markedView = (ImageView)v.findViewById(R.id.marked); - holder.publishedView = (ImageView)v.findViewById(R.id.published); - holder.excerptView = (TextView)v.findViewById(R.id.excerpt); - holder.flavorImageView = (ImageView) v.findViewById(R.id.flavor_image); - holder.authorView = (TextView)v.findViewById(R.id.author); - holder.dateView = (TextView) v.findViewById(R.id.date); - holder.selectionBoxView = (CheckBox) v.findViewById(R.id.selected); - holder.menuButtonView = (ImageView) v.findViewById(R.id.article_menu_button); - holder.flavorImageHolder = (ViewGroup) v.findViewById(R.id.flavorImageHolder); - - v.setTag(holder); - - // http://code.google.com/p/android/issues/detail?id=3414 - ((ViewGroup)v).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - } else { - holder = (HeadlineViewHolder) v.getTag(); - } - - if (holder.titleView != null) { + // only set active article when it makes sense (in HeadlinesActivity) + if (getActivity().findViewById(R.id.article_fragment) != null) { + m_activeArticle = article; + } + + m_adapter.notifyDataSetChanged(); + } + } + }); + + if (holder.titleView != null) { holder.titleView.setText(Html.fromHtml(article.title)); if (m_prefs.getBoolean("enable_condensed_fonts", false)) { @@ -982,8 +1057,6 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, } }); } - - return v; } private void adjustTitleTextView(int score, TextView tv, int position) { @@ -1067,7 +1140,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, return tmp; } - @Override + /* @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (!m_refreshInProgress && m_articles.findById(-1) != null && firstVisibleItem + visibleItemCount == m_articles.size()) { refresh(true); @@ -1093,7 +1166,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, m_readArticles.clear(); } } - } + } */ public Article getActiveArticle() { return m_activeArticle; diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/OnlineActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/OnlineActivity.java index c1adef52..10cc1c9a 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/OnlineActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/OnlineActivity.java @@ -152,11 +152,11 @@ public class OnlineActivity extends CommonActivity { setAppTheme(m_prefs); - super.onCreate(savedInstanceState); + if (canUseProgress()) { + requestWindowFeature(Window.FEATURE_PROGRESS); + } - if (canUseProgress()) { - requestWindowFeature(Window.FEATURE_PROGRESS); - } + super.onCreate(savedInstanceState); //requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/RecyclerArrayAdapter.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/RecyclerArrayAdapter.java new file mode 100644 index 00000000..2f6fdd82 --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/RecyclerArrayAdapter.java @@ -0,0 +1,94 @@ +package org.fox.ttrss.util; + +import android.support.v7.widget.RecyclerView; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Created by pascalwelsch on 04.07.14. + */ +public abstract class RecyclerArrayAdapter + extends RecyclerView.Adapter { + + protected List m_items; + + public RecyclerArrayAdapter(final List objects) { + m_items = objects; + } + + /** + * Adds the specified object at the end of the array. + * + * @param object The object to add at the end of the array. + */ + public void add(final T object) { + m_items.add(object); + notifyItemInserted(getItemCount() - 1); + } + + /** + * Remove all elements from the list. + */ + public void clear() { + final int size = getItemCount(); + m_items.clear(); + notifyItemRangeRemoved(0, size); + } + + @Override + public int getItemCount() { + return m_items.size(); + } + + public T getItem(final int position) { + return m_items.get(position); + } + + public long getItemId(final int position) { + return position; + } + + /** + * Returns the position of the specified item in the array. + * + * @param item The item to retrieve the position of. + * @return The position of the specified item. + */ + public int getPosition(final T item) { + return m_items.indexOf(item); + } + + /** + * Inserts the specified object at the specified index in the array. + * + * @param object The object to insert into the array. + * @param index The index at which the object must be inserted. + */ + public void insert(final T object, int index) { + m_items.add(index, object); + notifyItemInserted(index); + + } + + /** + * Removes the specified object from the array. + * + * @param object The object to remove. + */ + public void remove(T object) { + final int position = getPosition(object); + m_items.remove(object); + notifyItemRemoved(position); + } + + /** + * Sorts the content of this adapter using the specified comparator. + * + * @param comparator The comparator used to sort the objects contained in this adapter. + */ + public void sort(Comparator comparator) { + Collections.sort(m_items, comparator); + notifyItemRangeChanged(0, getItemCount()); + } +} \ No newline at end of file diff --git a/org.fox.ttrss/src/main/res/layout/headlines_fragment.xml b/org.fox.ttrss/src/main/res/layout/headlines_fragment.xml index 63f7f856..16e25520 100644 --- a/org.fox.ttrss/src/main/res/layout/headlines_fragment.xml +++ b/org.fox.ttrss/src/main/res/layout/headlines_fragment.xml @@ -9,15 +9,15 @@ android:layout_width="match_parent" android:layout_height="match_parent" > - - +