initial implementation of recyclerview

This commit is contained in:
Andrew Dolgov 2014-10-24 13:21:15 +04:00
parent 6e8c7a71fa
commit 7783ff3b42
7 changed files with 305 additions and 123 deletions

View File

@ -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')

View File

@ -78,13 +78,16 @@
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
</content>
<orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" />
<orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="cardview-v7-21.0.0" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-19.0.0" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-21.0.0" level="project" />
<orderEntry type="library" exported="" name="support-annotations-21.0.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-21.0.0" level="project" />
<orderEntry type="library" exported="" name="dashclock-api-r1.1" level="project" />
<orderEntry type="library" exported="" name="systembartint-1.0.3" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-19.1.0" level="project" />
<orderEntry type="library" exported="" name="jsoup-1.6.1" level="project" />
<orderEntry type="library" exported="" name="support-v4-19.1.0" level="project" />
<orderEntry type="library" exported="" name="library-1.3" level="project" />
<orderEntry type="library" exported="" name="gson-1.7.1" level="project" />
<orderEntry type="library" exported="" name="library-2.4.1" level="project" />

View File

@ -37,6 +37,8 @@
android:name=".FeedsActivity"
android:label="@string/app_name"
android:uiOptions="splitActionBarWhenNarrow" >
<meta-data android:name="android.support.UI_OPTIONS"
android:value="splitActionBarWhenNarrow" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
@ -45,6 +47,8 @@
android:name=".HeadlinesActivity"
android:label="@string/app_name"
android:uiOptions="splitActionBarWhenNarrow" >
<meta-data android:name="android.support.UI_OPTIONS"
android:value="splitActionBarWhenNarrow" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
@ -68,11 +72,17 @@
android:name=".offline.OfflineFeedsActivity"
android:label="@string/app_name"
android:uiOptions="splitActionBarWhenNarrow" >
<meta-data android:name="android.support.UI_OPTIONS"
android:value="splitActionBarWhenNarrow" />
</activity>
<activity
android:name=".offline.OfflineHeadlinesActivity"
android:label="@string/app_name"
android:uiOptions="splitActionBarWhenNarrow" >
<meta-data android:name="android.support.UI_OPTIONS"
android:value="splitActionBarWhenNarrow" />
</activity>
<activity
android:name=".share.ShareActivity"

View File

@ -13,6 +13,7 @@ import org.fox.ttrss.types.Article;
import org.fox.ttrss.types.ArticleList;
import org.fox.ttrss.types.Feed;
import org.fox.ttrss.util.HeadlinesRequest;
import org.fox.ttrss.util.RecyclerArrayAdapter;
import org.fox.ttrss.util.TypefaceCache;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
@ -33,6 +34,8 @@ import android.preference.PreferenceManager;
import android.provider.OpenableColumns;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Html;
import android.util.DisplayMetrics;
import android.util.Log;
@ -61,7 +64,7 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
public class HeadlinesFragment extends Fragment implements OnItemClickListener, OnScrollListener {
public class HeadlinesFragment extends Fragment {
public static enum ArticlesSelection { ALL, NONE, UNREAD };
public static final int HEADLINES_REQUEST_SIZE = 30;
@ -314,34 +317,59 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
}
ListView list = (ListView)view.findViewById(R.id.headlines);
m_adapter = new ArticleListAdapter(getActivity(), R.layout.headlines_row, (ArrayList<Article>)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<Article> {
private ArrayList<Article> items;
private class ArticleListAdapter extends RecyclerArrayAdapter<Article, RecyclerView.ViewHolder> {
//private ArrayList<Article> 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<Article> items) {
/* public ArticleListAdapter(Context context, int textViewResourceId, ArrayList<Article> 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<Article> 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;

View File

@ -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);

View File

@ -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<T, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
protected List<T> m_items;
public RecyclerArrayAdapter(final List<T> 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<? super T> comparator) {
Collections.sort(m_items, comparator);
notifyItemRangeChanged(0, getItemCount());
}
}

View File

@ -9,15 +9,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
<android.support.v7.widget.RecyclerView
android:id="@+id/headlines"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layoutAnimation="@anim/layout_headline"
android:dividerHeight="0dp"
android:divider="@null"
android:paddingTop="3dp"
android:layout_height="match_parent" >
</ListView>
</android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>
<LinearLayout