package org.goodev.discourse.ui; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.Loader; import android.text.TextUtils; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.BaseAdapter; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ListView; import android.widget.TextView; import com.manuelpeinado.refreshactionitem.ProgressIndicatorType; import com.manuelpeinado.refreshactionitem.RefreshActionItem; import com.manuelpeinado.refreshactionitem.RefreshActionItem.RefreshActionListener; import org.goodev.discourse.ActivityUtils; import org.goodev.discourse.App; import org.goodev.discourse.R; import org.goodev.discourse.api.Api; import org.goodev.discourse.api.LatestTopics; import org.goodev.discourse.api.LatestTopics.TopicList; import org.goodev.discourse.api.StarTask; import org.goodev.discourse.api.data.Topic; import org.goodev.discourse.utils.ImageLoader; import org.goodev.discourse.utils.Utils; public class TopicsListFragment extends ListFragment implements OnScrollListener, LoaderCallbacks<LatestTopics>, RefreshActionListener { public static final String EXTRA_ADD_VERTICAL_MARGINS = "extra_ADD_VERTICAL_MARGINS"; private static final String STATE_POSITION = "extra_postion"; private static final String STATE_TOP = "extra_top"; private static final int TOPICS_LOADER_ID = 1; private static final int HOLDER_TAG_KEY = 2; private final TopicAdapter mAdapter = new TopicAdapter(); protected String mSiteUrl; private LatestTopics mLatestTopics = new LatestTopics(); private int mListViewStatePosition; private int mListViewStateTop; private ImageLoader mImageLoader; private RefreshActionItem mRefreshActionItem; private StarTask mStarTask; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mImageLoader = App.getImageLoader(getActivity(), getResources()); if (savedInstanceState != null) { mSiteUrl = savedInstanceState.getString(Utils.EXTRA_URL); } else { Bundle arg = getArguments(); if (arg != null) { mSiteUrl = arg.getString(Utils.EXTRA_URL); } } setHasOptionsMenu(true); } @Override public void onAttach(Activity activity) { super.onAttach(activity); // onCreate 在该函数后调用 } public CharSequence getTitle() { return getResources().getString(R.string.title_latest); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { getActivity().setTitle(getTitle()); if (savedInstanceState != null) { mListViewStatePosition = savedInstanceState.getInt(STATE_POSITION, -1); mListViewStateTop = savedInstanceState.getInt(STATE_TOP, 0); } else { mListViewStatePosition = -1; mListViewStateTop = 0; } return super.onCreateView(inflater, container, savedInstanceState); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.fragment_topics_list, menu); MenuItem item = menu.findItem(R.id.action_refresh); mRefreshActionItem = (RefreshActionItem) item.getActionView(); mRefreshActionItem.setMenuItem(item); mRefreshActionItem.setProgressIndicatorType(ProgressIndicatorType.INDETERMINATE); // mRefreshActionItem.setMax(100); mRefreshActionItem.setRefreshActionListener(this); if (isStreamLoading()) { mRefreshActionItem.showProgress(true); } } @Override public void onRefreshButtonClick(RefreshActionItem sender) { mListViewStatePosition = -1; refresh(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { // case R.id.action_refresh: // return true; } return super.onOptionsItemSelected(item); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setEmptyText(getString(R.string.empty_topics)); // In support library r8, calling initLoader for a fragment in a FragmentPagerAdapter // in the fragment's onCreate may cause the same LoaderManager to be dealt to multiple // fragments because their mIndex is -1 (haven't been added to the activity yet). Thus, // we do this in onActivityCreated. getLoaderManager().initLoader(TOPICS_LOADER_ID, null, this); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); final ListView listView = getListView(); if (!Utils.isTablet(getActivity())) { // TODO ... // view.setBackgroundColor(getResources().getColor(R.color.plus_stream_spacer_color)); } if (getArguments() != null && getArguments().getBoolean(EXTRA_ADD_VERTICAL_MARGINS, false)) { int verticalMargin = getResources().getDimensionPixelSize(R.dimen.topics_list_padding_vertical); if (verticalMargin > 0) { listView.setClipToPadding(false); listView.setPadding(0, verticalMargin, 0, verticalMargin); } } listView.setOnScrollListener(this); listView.setDrawSelectorOnTop(true); // listView.setDivider(getResources().getDrawable(android.R.color.transparent)); // listView.setDividerHeight(getResources() // .getDimensionPixelSize(R.dimen.page_margin_width)); TypedValue v = new TypedValue(); getActivity().getTheme().resolveAttribute(R.attr.clickableItemBackground, v, true); listView.setSelector(v.resourceId); setListAdapter(mAdapter); } @Override public void onSaveInstanceState(Bundle outState) { outState.putString(Utils.EXTRA_URL, mSiteUrl); if (isAdded()) { View v = getListView().getChildAt(0); int top = (v == null) ? 0 : v.getTop(); outState.putInt(STATE_POSITION, getListView().getFirstVisiblePosition()); outState.putInt(STATE_TOP, top); } super.onSaveInstanceState(outState); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // Pause disk cache access to ensure smoother scrolling if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) { mImageLoader.stopProcessingQueue(); } else { mImageLoader.startProcessingQueue(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (!isStreamLoading() && streamHasMoreResults() && visibleItemCount != 0 && firstVisibleItem + visibleItemCount >= totalItemCount - 1) { loadMoreResults(); } } public void refresh() { if (mRefreshActionItem != null) mRefreshActionItem.showProgress(true); refresh(false); } public void refresh(boolean forceRefresh) { if (isStreamLoading() && !forceRefresh) { return; } // clear current items mLatestTopics.clear(); mAdapter.notifyDataSetInvalidated(); if (isAdded()) { Loader loader = getLoaderManager().getLoader(TOPICS_LOADER_ID); ((LatestTopicsLoader) loader).init(mSiteUrl, getInitUrl()); } loadMoreResults(); } public void loadMoreResults() { if (isAdded()) { Loader loader = getLoaderManager().getLoader(TOPICS_LOADER_ID); if (loader != null) { loader.forceLoad(); } } } @Override public Loader<LatestTopics> onCreateLoader(int id, Bundle args) { return new LatestTopicsLoader(getActivity(), mSiteUrl, getInitUrl()); } @Override public void onLoadFinished(Loader<LatestTopics> loader, LatestTopics data) { if (mRefreshActionItem != null) { mRefreshActionItem.showProgress(false); } if (data != null) { mLatestTopics = data; } mAdapter.notifyDataSetChanged(); if (mListViewStatePosition != -1 && isAdded()) { getListView().setSelectionFromTop(mListViewStatePosition, mListViewStateTop); mListViewStatePosition = -1; } } @Override public void onLoaderReset(Loader<LatestTopics> loader) { } private boolean isStreamLoading() { if (isAdded()) { final Loader loader = getLoaderManager().getLoader(TOPICS_LOADER_ID); if (loader != null) { return ((LatestTopicsLoader) loader).isLoading(); } } return true; } private boolean streamHasMoreResults() { if (isAdded()) { final Loader loader = getLoaderManager().getLoader(TOPICS_LOADER_ID); if (loader != null) { return ((LatestTopicsLoader) loader).hasMoreResults(); } } return false; } private boolean streamHasError() { if (isAdded()) { final Loader loader = getLoaderManager().getLoader(TOPICS_LOADER_ID); if (loader != null) { return ((LatestTopicsLoader) loader).hasError(); } } return false; } public String getInitUrl() { return mSiteUrl + Api.LATEST; } public String getPopularUrl() { return mSiteUrl + Api.POPULAR; } @Override public void onListItemClick(ListView l, View v, int position, long id) { Topic t = (Topic) l.getItemAtPosition(position); if (t == null) { return; } ActivityUtils.startTopicActivity(getActivity(), t.slug, t.id); } private static class LatestTopicsLoader extends AsyncTaskLoader<LatestTopics> { LatestTopics mLatestTopics; private boolean mIsLoading; private boolean mHasError; private String mSiteUrl; private String mInitUrl; public LatestTopicsLoader(Context context, String siteUrl, String initUrl) { super(context); init(siteUrl, initUrl); } private void init(String siteUrl, String initUrl) { mHasError = false; mIsLoading = true; mSiteUrl = siteUrl; mInitUrl = initUrl; mLatestTopics = null; } @Override public LatestTopics loadInBackground() { mIsLoading = true; if (mLatestTopics == null) { mLatestTopics = new LatestTopics(); } TopicList tl = mLatestTopics.getTopicList(); String url; if (tl != null && !TextUtils.isEmpty(tl.more_topics_url)) { url = Utils.removeLastSlash(mSiteUrl) + tl.more_topics_url; } else { url = mInitUrl; } boolean result = Api.getLatestTopics(mLatestTopics, url); // if(!result && first) { // result = Api.getLatestTopics(mLatestTopics, mSiteUrl+Api.POPULAR); // } mHasError = !result; return result ? mLatestTopics : null; } @Override public void deliverResult(LatestTopics data) { mIsLoading = false; if (data != null) { if (mLatestTopics == null) { mLatestTopics = data; } else { // mLatestTopics 在 loadInBackground 函数中重用了 里面已经设置好数据了, 不用调用addAll()函数了 // mLatestTopics.addAll(data); } } if (isStarted()) { // Need to return new ArrayList for some reason or onLoadFinished() is not called super.deliverResult(mLatestTopics == null ? null : new LatestTopics(mLatestTopics)); } } @Override protected void onStartLoading() { if (mLatestTopics != null) { // If we already have results and are starting up, deliver what we already have. deliverResult(null); } else { forceLoad(); } } @Override protected void onStopLoading() { mIsLoading = false; cancelLoad(); } @Override protected void onReset() { super.onReset(); onStopLoading(); mLatestTopics = null; } public boolean isLoading() { return mIsLoading; } public boolean hasMoreResults() { return mLatestTopics != null && mLatestTopics.getTopicList() != null && mLatestTopics.getTopicList().more_topics_url != null; } public boolean hasError() { return mHasError; } public void refresh() { reset(); startLoading(); } } private class TopicAdapter extends BaseAdapter { private static final int VIEW_TYPE_ITEM = 0; private static final int VIEW_TYPE_LOADING = 1; @Override public boolean areAllItemsEnabled() { return false; } @Override public boolean isEnabled(int position) { return getItemViewType(position) == VIEW_TYPE_ITEM; } @Override public int getViewTypeCount() { return 2; } @Override public boolean hasStableIds() { return true; } @Override public int getCount() { return mLatestTopics.getTopicSize() + ( // show the status list row if... ((isStreamLoading() && mLatestTopics.getTopicSize() == 0) // ...this is the first load || streamHasMoreResults() // ...or there's another page || streamHasError()) // ...or there's an error ? 1 : 0); } @Override public int getItemViewType(int position) { return (position >= mLatestTopics.getTopicSize()) ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM; } @Override public Object getItem(int position) { return (getItemViewType(position) == VIEW_TYPE_ITEM) ? mLatestTopics.getTopic(position) : null; } @Override public long getItemId(int position) { // TODO: better unique ID heuristic return (getItemViewType(position) == VIEW_TYPE_ITEM) ? mLatestTopics.getTopic(position).id.hashCode() : -1; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (getItemViewType(position) == VIEW_TYPE_LOADING) { if (convertView == null) { convertView = getLayoutInflater(null).inflate(R.layout.list_item_stream_status, parent, false); } if (streamHasError()) { convertView.findViewById(android.R.id.progress).setVisibility(View.GONE); ((TextView) convertView.findViewById(android.R.id.text1)).setText(R.string.stream_error); } else { convertView.findViewById(android.R.id.progress).setVisibility(View.VISIBLE); ((TextView) convertView.findViewById(android.R.id.text1)).setText(R.string.loading); } return convertView; } else { Topic topic = mLatestTopics.getTopic(position); if (convertView == null) { convertView = getLayoutInflater(null).inflate(R.layout.topic_list_item, parent, false); } TopicRowViewBinder.bindItemView(convertView, topic, mImageLoader, false, mLatestTopics, mStarListener); return convertView; } } } private final OnCheckedChangeListener mStarListener = new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { Topic t = (Topic) buttonView.getTag(); if (t == null || !App.isLogin()) { return; } if (mStarTask != null && !mStarTask.isCancelled()) { mStarTask.cancel(true); } mStarTask = new StarTask(buttonView, mStarListener, mSiteUrl, t.slug, t.id, isChecked); mStarTask.execute(); } }; }