diff --git a/twidere/src/androidTest/AndroidManifest.xml b/twidere/src/androidTest/AndroidManifest.xml new file mode 100644 index 000000000..02b4f4eae --- /dev/null +++ b/twidere/src/androidTest/AndroidManifest.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/twidere/src/androidTest/java/org/mariotaku/twidere/ApplicationTest.java b/twidere/src/androidTest/java/org/mariotaku/twidere/ApplicationTest.java deleted file mode 100644 index 2436044f1..000000000 --- a/twidere/src/androidTest/java/org/mariotaku/twidere/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.mariotaku.myapplication; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/twidere/src/androidTest/java/org/mariotaku/twidere/test/TwidereApplicationTest.java b/twidere/src/androidTest/java/org/mariotaku/twidere/test/TwidereApplicationTest.java new file mode 100644 index 000000000..1dd6cb22a --- /dev/null +++ b/twidere/src/androidTest/java/org/mariotaku/twidere/test/TwidereApplicationTest.java @@ -0,0 +1,20 @@ +package org.mariotaku.twidere.test; + +import android.test.ApplicationTestCase; + +import org.mariotaku.twidere.app.TwidereApplication; + +/** + * Testing Fundamentals + */ +public class TwidereApplicationTest extends ApplicationTestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + public TwidereApplicationTest() { + super(TwidereApplication.class); + } + +} \ No newline at end of file diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportThemedActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportThemedActivity.java index 34400fc0f..b998e7734 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportThemedActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportThemedActivity.java @@ -97,12 +97,12 @@ public abstract class BaseSupportThemedActivity extends FragmentActivity impleme @Override protected void onTitleChanged(CharSequence title, int color) { final SpannableStringBuilder builder = new SpannableStringBuilder(title); - super.onTitleChanged(title, color); final int themeResId = getCurrentThemeResourceId(); final int themeColor = getThemeColor(), contrastColor = Utils.getContrastYIQ(themeColor, 192); if (!ThemeUtils.isDarkTheme(themeResId)) { builder.setSpan(new ForegroundColorSpan(contrastColor), 0, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } + super.onTitleChanged(title, color); } @Override diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java index 517419221..45641869d 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java @@ -105,7 +105,7 @@ import org.mariotaku.twidere.util.ThemeUtils; import org.mariotaku.twidere.util.TwidereValidator; import org.mariotaku.twidere.util.Utils; import org.mariotaku.twidere.util.accessor.ViewAccessor; -import org.mariotaku.twidere.view.ColorLabelFrameLayout; +import org.mariotaku.twidere.view.ComposeSelectAccountButton; import org.mariotaku.twidere.view.StatusTextCountView; import org.mariotaku.twidere.view.TwidereMenuBar; import org.mariotaku.twidere.view.holder.StatusListViewHolder; @@ -132,7 +132,6 @@ import static org.mariotaku.twidere.util.ThemeUtils.getWindowContentOverlayForCo import static org.mariotaku.twidere.util.UserColorNicknameUtils.getUserColor; import static org.mariotaku.twidere.util.Utils.addIntentToMenu; import static org.mariotaku.twidere.util.Utils.copyStream; -import static org.mariotaku.twidere.util.Utils.getAccountColors; import static org.mariotaku.twidere.util.Utils.getAccountIds; import static org.mariotaku.twidere.util.Utils.getAccountName; import static org.mariotaku.twidere.util.Utils.getAccountScreenName; @@ -180,7 +179,7 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa private ProgressBar mProgress; private View mSendView; private StatusTextCountView mSendTextCountView; - private ColorLabelFrameLayout mSelectAccountButton; + private ComposeSelectAccountButton mSelectAccountAccounts; private MediaPreviewAdapter mMediaPreviewAdapter; @@ -438,7 +437,7 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa final View composeBottomBar = findViewById(R.id.compose_bottombar); mSendView = composeBottomBar.findViewById(R.id.send); mSendTextCountView = (StatusTextCountView) mSendView.findViewById(R.id.status_text_count); - mSelectAccountButton = (ColorLabelFrameLayout) composeActionBar.findViewById(R.id.select_account); + mSelectAccountAccounts = (ComposeSelectAccountButton) composeActionBar.findViewById(R.id.select_account); ViewAccessor.setBackground(findViewById(R.id.compose_content), getWindowContentOverlayForCompose(this)); ViewAccessor.setBackground(composeActionBar, getActionBarBackground(this, getCurrentThemeResourceId())); } @@ -581,12 +580,12 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa mAccountSelectorPopup.setModal(true); mAccountSelectorPopup.setContentWidth(getResources().getDimensionPixelSize(R.dimen.account_selector_popup_width)); mAccountSelectorPopup.setAdapter(accountAdapter); - mAccountSelectorPopup.setAnchorView(mSelectAccountButton); + mAccountSelectorPopup.setAnchorView(mSelectAccountAccounts); // mSelectAccountButton.setOnTouchListener(ListPopupWindowCompat.createDragToOpenListener( // mAccountSelectorPopup, mSelectAccountButton)); - mSelectAccountButton.setOnClickListener(this); - mSelectAccountButton.setOnLongClickListener(this); + mSelectAccountAccounts.setOnClickListener(this); + mSelectAccountAccounts.setOnLongClickListener(this); mMediaPreviewAdapter = new MediaPreviewAdapter(this); mMediaPreviewGrid.setAdapter(mMediaPreviewAdapter); @@ -1032,7 +1031,7 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa editor.putString(KEY_COMPOSE_ACCOUNTS, ArrayUtils.toString(mSendAccountIds, ',', false)); editor.apply(); } - mSelectAccountButton.drawEnd(getAccountColors(this, mSendAccountIds)); + mSelectAccountAccounts.setSelectedAccounts(mSendAccountIds); } private void updateMediaPreview() { @@ -1492,4 +1491,6 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa return Collections.unmodifiableList(getObjects()); } } + + } diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/HomeActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/HomeActivity.java index ea5f009d5..3bcf53199 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/HomeActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/HomeActivity.java @@ -105,9 +105,6 @@ import org.mariotaku.twidere.view.TabPagerIndicator; import org.mariotaku.twidere.view.TintedStatusFrameLayout; import org.mariotaku.twidere.view.iface.IHomeActionButton; -import java.util.ArrayList; -import java.util.List; - import edu.ucdavis.earlybird.ProfilingUtil; import static org.mariotaku.twidere.util.CompareUtils.classEquals; @@ -132,8 +129,6 @@ public class HomeActivity extends BaseSupportActivity implements OnClickListener private final ContentObserver mAccountChangeObserver = new AccountChangeObserver(this, mHandler); - private final ArrayList mCustomTabs = new ArrayList<>(); - private final SparseArray mAttachedFragments = new SparseArray<>(); private ParcelableAccount mSelectedAccountToSearch; @@ -645,7 +640,7 @@ public class HomeActivity extends BaseSupportActivity implements OnClickListener resolver.registerContentObserver(Accounts.CONTENT_URI, true, mAccountChangeObserver); final Bus bus = TwidereApplication.getInstance(this).getMessageBus(); bus.register(this); - if (isTabsChanged(getHomeTabs(this)) || getTabDisplayOptionInt(this) != mTabDisplayOption) { + if (getTabDisplayOptionInt(this) != mTabDisplayOption) { restart(); } // UCD @@ -725,13 +720,11 @@ public class HomeActivity extends BaseSupportActivity implements OnClickListener } private void setupHomeTabs() { - final List tabs = getHomeTabs(this); - mCustomTabs.clear(); - mCustomTabs.addAll(tabs); mPagerAdapter.clear(); - mPagerAdapter.addTabs(tabs); - mEmptyTabHint.setVisibility(tabs.isEmpty() ? View.VISIBLE : View.GONE); - mViewPager.setVisibility(tabs.isEmpty() ? View.GONE : View.VISIBLE); + mPagerAdapter.addTabs(getHomeTabs(this)); + final boolean hasNoTab = mPagerAdapter.getCount() == 0; + mEmptyTabHint.setVisibility(hasNoTab ? View.VISIBLE : View.GONE); + mViewPager.setVisibility(hasNoTab ? View.GONE : View.VISIBLE); } private void initUnreadCount() { @@ -740,15 +733,6 @@ public class HomeActivity extends BaseSupportActivity implements OnClickListener } } - private boolean isTabsChanged(final List tabs) { - if (mCustomTabs.size() == 0 && tabs == null) return false; - if (mCustomTabs.size() != tabs.size()) return true; - for (int i = 0, size = mCustomTabs.size(); i < size; i++) { - if (!mCustomTabs.get(i).equals(tabs.get(i))) return true; - } - return false; - } - private void openAccountsDrawer() { if (mSlidingMenu == null) return; mSlidingMenu.showMenu(); diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/LinkHandlerActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/LinkHandlerActivity.java index 1cbe3f332..ea224abe2 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/LinkHandlerActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/LinkHandlerActivity.java @@ -211,10 +211,10 @@ public class LinkHandlerActivity extends BaseSupportActivity implements OnClickL transitionRes = R.transition.transition_user; break; } - case LINK_ID_STATUS: { - transitionRes = R.transition.transition_status; - break; - } +// case LINK_ID_STATUS: { +// transitionRes = R.transition.transition_status; +// break; +// } default: { transitionRes = 0; break; diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsStatusesAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsStatusesAdapter.java index 926e86cfe..8f13ed5e9 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsStatusesAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsStatusesAdapter.java @@ -40,7 +40,7 @@ public abstract class AbsStatusesAdapter extends Adapter implemen private final int mCardLayoutResource; private boolean mLoadMoreIndicatorEnabled; - private EventListener mEventListener; + private StatusAdapterListener mStatusAdapterListener; public AbsStatusesAdapter(Context context, boolean compact) { mContext = context; @@ -127,8 +127,8 @@ public abstract class AbsStatusesAdapter extends Adapter implemen @Override public final void onStatusClick(StatusViewHolder holder, int position) { - if (mEventListener != null) { - mEventListener.onStatusClick(holder, position); + if (mStatusAdapterListener != null) { + mStatusAdapterListener.onStatusClick(holder, position); } } @@ -150,15 +150,15 @@ public abstract class AbsStatusesAdapter extends Adapter implemen @Override public void onItemActionClick(ViewHolder holder, int id, int position) { - if (mEventListener != null) { - mEventListener.onStatusActionClick((StatusViewHolder) holder, id, position); + if (mStatusAdapterListener != null) { + mStatusAdapterListener.onStatusActionClick((StatusViewHolder) holder, id, position); } } @Override public void onItemMenuClick(ViewHolder holder, int position) { - if (mEventListener != null) { - mEventListener.onStatusMenuClick((StatusViewHolder) holder, position); + if (mStatusAdapterListener != null) { + mStatusAdapterListener.onStatusMenuClick((StatusViewHolder) holder, position); } } @@ -166,18 +166,18 @@ public abstract class AbsStatusesAdapter extends Adapter implemen public abstract D getData(); - public void setEventListener(EventListener listener) { - mEventListener = listener; + public void setEventListener(StatusAdapterListener listener) { + mStatusAdapterListener = listener; } @Override public final void onGapClick(ViewHolder holder, int position) { - if (mEventListener != null) { - mEventListener.onGapClick((GapViewHolder) holder, position); + if (mStatusAdapterListener != null) { + mStatusAdapterListener.onGapClick((GapViewHolder) holder, position); } } - public static interface EventListener { + public static interface StatusAdapterListener { void onStatusActionClick(StatusViewHolder holder, int id, int position); void onStatusClick(StatusViewHolder holder, int position); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java index d871f4c62..f81b728a4 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java @@ -8,10 +8,8 @@ import android.content.SharedPreferences; import android.graphics.Rect; import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; -import android.support.v4.util.Pair; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener; import android.support.v7.widget.LinearLayoutManager; @@ -23,7 +21,7 @@ import android.view.ViewGroup; import org.mariotaku.twidere.R; import org.mariotaku.twidere.adapter.AbsStatusesAdapter; -import org.mariotaku.twidere.adapter.AbsStatusesAdapter.EventListener; +import org.mariotaku.twidere.adapter.AbsStatusesAdapter.StatusAdapterListener; import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration; import org.mariotaku.twidere.constant.IntentConstants; import org.mariotaku.twidere.fragment.iface.RefreshScrollTopInterface; @@ -40,7 +38,7 @@ import org.mariotaku.twidere.view.holder.StatusViewHolder; * Created by mariotaku on 14/11/5. */ public abstract class AbsStatusesFragment extends BaseSupportFragment implements LoaderCallbacks, - OnRefreshListener, DrawerCallback, RefreshScrollTopInterface, EventListener { + OnRefreshListener, DrawerCallback, RefreshScrollTopInterface, StatusAdapterListener { private final BroadcastReceiver mStateReceiver = new BroadcastReceiver() { @@ -204,19 +202,7 @@ public abstract class AbsStatusesFragment extends BaseSupportFragment impl @Override public void onStatusClick(StatusViewHolder holder, int position) { - final FragmentActivity activity = getActivity(); - final Bundle activityOption = Utils.makeSceneTransitionOption(activity, - new Pair(holder.getProfileImageView(), StatusFragment.TRANSITION_NAME_PROFILE_IMAGE), - new Pair(holder.getProfileTypeView(), StatusFragment.TRANSITION_NAME_PROFILE_TYPE)); - Utils.openStatus(getActivity(), mAdapter.getStatus(position), activityOption); -// final View cardView = holder.getCardView(); -// if (cardView != null && context instanceof FragmentActivity) { -// final Bundle options = Utils.makeSceneTransitionOption((FragmentActivity) context, -// new Pair<>(cardView, StatusFragment.TRANSITION_NAME_CARD)); -// Utils.openStatus(context, getStatus(position), options); -// } else { -// Utils.openStatus(context, getStatus(position), null); -// } + Utils.openStatus(getActivity(), mAdapter.getStatus(position), null); } @Override diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDashboardFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDashboardFragment.java index 130715dd1..e0b8e222c 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDashboardFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDashboardFragment.java @@ -494,7 +494,7 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement @Override public AccountProfileImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - final View view = mInflater.inflate(R.layout.adapter_item_compose_account, parent, false); + final View view = mInflater.inflate(R.layout.adapter_item_dashboard_account, parent, false); return new AccountProfileImageViewHolder(this, view); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java index 903a8323e..7c7a3cd88 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java @@ -19,11 +19,13 @@ package org.mariotaku.twidere.fragment.support; +import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; +import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; import android.support.annotation.Nullable; @@ -32,7 +34,6 @@ import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; import android.support.v4.util.Pair; -import android.support.v4.view.ViewCompat; import android.support.v7.widget.CardView; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -73,6 +74,7 @@ import org.mariotaku.twidere.task.TwidereAsyncTask.Status; import org.mariotaku.twidere.text.method.StatusContentMovementMethod; import org.mariotaku.twidere.util.AsyncTwitterWrapper; import org.mariotaku.twidere.util.ClipboardUtils; +import org.mariotaku.twidere.util.CompareUtils; import org.mariotaku.twidere.util.ImageLoaderWrapper; import org.mariotaku.twidere.util.ImageLoadingHandler; import org.mariotaku.twidere.util.MediaPreviewUtils; @@ -94,15 +96,18 @@ import java.util.Locale; import twitter4j.TwitterException; import static android.text.TextUtils.isEmpty; +import static org.mariotaku.twidere.util.UserColorNicknameUtils.clearUserColor; import static org.mariotaku.twidere.util.UserColorNicknameUtils.clearUserNickname; import static org.mariotaku.twidere.util.UserColorNicknameUtils.getUserColor; import static org.mariotaku.twidere.util.UserColorNicknameUtils.getUserNickname; +import static org.mariotaku.twidere.util.UserColorNicknameUtils.setUserColor; import static org.mariotaku.twidere.util.Utils.cancelRetweet; import static org.mariotaku.twidere.util.Utils.findStatus; import static org.mariotaku.twidere.util.Utils.formatToLongTimeString; import static org.mariotaku.twidere.util.Utils.getLocalizedNumber; import static org.mariotaku.twidere.util.Utils.getUserTypeIconRes; import static org.mariotaku.twidere.util.Utils.isMyRetweet; +import static org.mariotaku.twidere.util.Utils.openStatus; import static org.mariotaku.twidere.util.Utils.openUserProfile; import static org.mariotaku.twidere.util.Utils.setMenuForStatus; import static org.mariotaku.twidere.util.Utils.showErrorMessage; @@ -115,9 +120,6 @@ import static org.mariotaku.twidere.util.Utils.startStatusShareChooser; public class StatusFragment extends BaseSupportFragment implements LoaderCallbacks>, OnMediaClickListener { - public static final String TRANSITION_NAME_PROFILE_IMAGE = "profile_image"; - public static final String TRANSITION_NAME_PROFILE_TYPE = "profile_type"; - private static final int LOADER_ID_DETAIL_STATUS = 1; private static final int LOADER_ID_STATUS_REPLIES = 2; @@ -153,6 +155,34 @@ public class StatusFragment extends BaseSupportFragment }; private LinearLayoutManager mLayoutManager; + @Override + public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { + switch (requestCode) { + case REQUEST_SET_COLOR: { + final ParcelableStatus status = mStatusAdapter.getStatus(); + if (status == null) return; + if (resultCode == Activity.RESULT_OK) { + if (data == null) return; + final int color = data.getIntExtra(EXTRA_COLOR, Color.TRANSPARENT); + setUserColor(getActivity(), status.user_id, color); + } else if (resultCode == ColorPickerDialogActivity.RESULT_CLEARED) { + clearUserColor(getActivity(), status.user_id); + } + break; + } + case REQUEST_SELECT_ACCOUNT: { + final ParcelableStatus status = mStatusAdapter.getStatus(); + if (status == null) return; + if (resultCode == Activity.RESULT_OK) { + if (data == null || !data.hasExtra(EXTRA_ID)) return; + final long accountId = data.getLongExtra(EXTRA_ID, -1); + openStatus(getActivity(), accountId, status.id); + } + break; + } + } + } + @Override public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { @@ -174,8 +204,7 @@ public class StatusFragment extends BaseSupportFragment if (view == null) throw new AssertionError(); final Context context = view.getContext(); final boolean compact = Utils.isCompactCards(context); - mLayoutManager = new MyLinearLayoutManager(context, mRecyclerView); - mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); + mLayoutManager = new StatusListLinearLayoutManager(context, mRecyclerView); if (compact) { mRecyclerView.addItemDecoration(new DividerItemDecoration(context, mLayoutManager.getOrientation())); } @@ -189,24 +218,31 @@ public class StatusFragment extends BaseSupportFragment } @Override - public void onLoadFinished(final Loader> loader, - final SingleResponse data) { - if (data.hasData()) { - final ParcelableStatus status = data.getData(); - mStatusAdapter.setConversation(null); - mStatusAdapter.setReplies(null); - mStatusAdapter.setStatus(status); - mLayoutManager.scrollToPositionWithOffset(1, 0); - loadReplies(status); - loadConversation(status); - } else { - //TODO show errors - } + public void onMediaClick(View view, ParcelableMedia media) { + } @Override - public void onMediaClick(View view, ParcelableMedia media) { - + public void onLoadFinished(final Loader> loader, + final SingleResponse data) { + if (data.hasData()) { + final long itemId = mStatusAdapter.getItemId(mLayoutManager.findFirstVisibleItemPosition()); + final View firstChild = mLayoutManager.getChildAt(0); + final int top = firstChild != null ? firstChild.getTop() : 0; + final ParcelableStatus status = data.getData(); + if (mStatusAdapter.setStatus(status)) { + mLayoutManager.scrollToPositionWithOffset(1, 0); + mStatusAdapter.setConversation(null); + mStatusAdapter.setReplies(null); + loadReplies(status); + loadConversation(status); + } else { + final int position = mStatusAdapter.findPositionById(itemId); + mLayoutManager.scrollToPositionWithOffset(position, top); + } + } else { + //TODO show errors + } } @Override @@ -222,11 +258,6 @@ public class StatusFragment extends BaseSupportFragment getView().setPadding(insets.left, insets.top, insets.right, insets.bottom); } - @Override - public void onLoaderReset(final Loader> loader) { - - } - private void loadConversation(ParcelableStatus status) { if (mLoadConversationTask != null && mLoadConversationTask.getStatus() == Status.RUNNING) { mLoadConversationTask.cancel(true); @@ -249,6 +280,11 @@ public class StatusFragment extends BaseSupportFragment mRepliesLoaderInitialized = true; } + @Override + public void onLoaderReset(final Loader> loader) { + + } + private void setConversation(List data) { if (mLayoutManager.getChildCount() != 0) { final long itemId = mStatusAdapter.getItemId(mLayoutManager.findFirstVisibleItemPosition()); @@ -356,7 +392,7 @@ public class StatusFragment extends BaseSupportFragment @Override public void onStatusClick(StatusViewHolder holder, int position) { - + openStatus(mFragment.getActivity(), getStatus(position), null); } @Override @@ -369,6 +405,17 @@ public class StatusFragment extends BaseSupportFragment } + public ParcelableStatus getStatus() { + return mStatus; + } + + public boolean setStatus(ParcelableStatus status) { + final ParcelableStatus old = mStatus; + mStatus = status; + notifyDataSetChanged(); + return !CompareUtils.objectEquals(old, status); + } + public boolean isDetailMediaExpanded() { return mDetailMediaExpanded; } @@ -491,11 +538,6 @@ public class StatusFragment extends BaseSupportFragment notifyDataSetChanged(); } - public void setStatus(ParcelableStatus status) { - mStatus = status; - notifyDataSetChanged(); - } - private int getConversationCount() { return mConversation != null ? mConversation.size() : 1; } @@ -505,6 +547,7 @@ public class StatusFragment extends BaseSupportFragment } } + static class LoadConversationTask extends TwidereAsyncTask> { @@ -666,13 +709,13 @@ public class StatusFragment extends BaseSupportFragment case MENU_QUOTE: { final Intent intent = new Intent(INTENT_ACTION_QUOTE); intent.putExtra(EXTRA_STATUS, status); - activity.startActivity(intent); + fragment.startActivity(intent); break; } case MENU_REPLY: { final Intent intent = new Intent(INTENT_ACTION_REPLY); intent.putExtra(EXTRA_STATUS, status); - activity.startActivity(intent); + fragment.startActivity(intent); break; } case MENU_FAVORITE: { @@ -726,13 +769,13 @@ public class StatusFragment extends BaseSupportFragment final Intent intent = new Intent(INTENT_ACTION_SELECT_ACCOUNT); intent.setClass(activity, AccountSelectorActivity.class); intent.putExtra(EXTRA_SINGLE_SELECTION, true); - activity.startActivityForResult(intent, REQUEST_SELECT_ACCOUNT); + fragment.startActivityForResult(intent, REQUEST_SELECT_ACCOUNT); break; } default: { if (item.getIntent() != null) { try { - activity.startActivity(item.getIntent()); + fragment.startActivity(item.getIntent()); } catch (final ActivityNotFoundException e) { Log.w(LOGTAG, e); return false; @@ -744,11 +787,6 @@ public class StatusFragment extends BaseSupportFragment return true; } - @Override - public void onMediaClick(View view, ParcelableMedia media) { - adapter.mFragment.onMediaClick(view, media); - } - public void showStatus(ParcelableStatus status) { if (status == null) return; progressContainer.setVisibility(View.GONE); @@ -830,28 +868,39 @@ public class StatusFragment extends BaseSupportFragment menuBar.show(); } + @Override + public void onMediaClick(View view, ParcelableMedia media) { + adapter.mFragment.onMediaClick(view, media); + } + private void initViews() { menuBar.setOnMenuItemClickListener(this); menuBar.inflate(R.menu.menu_status); mediaPreviewLoad.setOnClickListener(this); profileContainer.setOnClickListener(this); - ViewCompat.setTransitionName(profileImageView, TRANSITION_NAME_PROFILE_IMAGE); - ViewCompat.setTransitionName(profileTypeView, TRANSITION_NAME_PROFILE_TYPE); } } - private static class MyLinearLayoutManager extends LinearLayoutManager { + private static class StatusListLinearLayoutManager extends LinearLayoutManager { private final RecyclerView recyclerView; - public MyLinearLayoutManager(Context context, RecyclerView recyclerView) { + public StatusListLinearLayoutManager(Context context, RecyclerView recyclerView) { super(context); + setOrientation(LinearLayoutManager.VERTICAL); this.recyclerView = recyclerView; } + @Override + public void setOrientation(int orientation) { + if (orientation != VERTICAL) + throw new IllegalArgumentException("Only VERTICAL orientation supported"); + super.setOrientation(orientation); + } + @Override public int getDecoratedMeasuredHeight(View child) { final int height = super.getDecoratedMeasuredHeight(child); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java index 565fad91c..2bccc487a 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java @@ -78,6 +78,7 @@ import com.squareup.otto.Subscribe; import org.mariotaku.menucomponent.internal.menu.MenuUtils; import org.mariotaku.querybuilder.Expression; import org.mariotaku.twidere.R; +import org.mariotaku.twidere.activity.iface.IThemedActivity; import org.mariotaku.twidere.activity.support.AccountSelectorActivity; import org.mariotaku.twidere.activity.support.ColorPickerDialogActivity; import org.mariotaku.twidere.activity.support.LinkHandlerActivity; @@ -87,6 +88,7 @@ import org.mariotaku.twidere.adapter.support.SupportTabsAdapter; import org.mariotaku.twidere.app.TwidereApplication; import org.mariotaku.twidere.fragment.iface.IBaseFragment.SystemWindowsInsetsCallback; import org.mariotaku.twidere.fragment.iface.SupportFragmentCallback; +import org.mariotaku.twidere.graphic.ActionBarColorDrawable; import org.mariotaku.twidere.loader.support.ParcelableUserLoader; import org.mariotaku.twidere.model.ParcelableUser; import org.mariotaku.twidere.model.ParcelableUserList; @@ -204,9 +206,8 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener @Subscribe public void notifyFriendshipUpdated(FriendshipUpdatedEvent event) { - if (event.user != null && event.user.equals(mUser)) { - getFriendship(); - } + if (!event.user.equals(mUser)) return; + getFriendship(); } private void updateRefreshState() { @@ -344,7 +345,6 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener }; public void displayUser(final ParcelableUser user) { - mRelationship = null; mUser = null; if (user == null || user.id <= 0 || getActivity() == null) return; final Resources res = getResources(); @@ -853,7 +853,15 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener protected void fitSystemWindows(Rect insets) { super.fitSystemWindows(insets); mHeaderDrawerLayout.setPadding(insets.left, insets.top, insets.right, insets.bottom); - mHeaderDrawerLayout.setClipToPadding(ThemeUtils.isTransparentBackground(getActivity())); + final FragmentActivity activity = getActivity(); + final boolean isTransparentBackground; + if (activity instanceof IThemedActivity) { + final int themeRes = ((IThemedActivity) activity).getCurrentThemeResourceId(); + isTransparentBackground = ThemeUtils.isTransparentBackground(themeRes); + } else { + isTransparentBackground = ThemeUtils.isTransparentBackground(getActivity()); + } + mHeaderDrawerLayout.setClipToPadding(isTransparentBackground); } public void setListShown(boolean shown) { @@ -867,6 +875,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener } private void getFriendship() { + mRelationship = null; final ParcelableUser user = mUser; final LoaderManager lm = getLoaderManager(); lm.destroyLoader(LOADER_ID_FRIENDSHIP); @@ -1225,7 +1234,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener public ActionBarDrawable(Resources resources, Drawable shadow, Drawable background, boolean colorLineOnly) { super(new Drawable[]{shadow, background, new LineBackgroundDrawable(resources, 2.0f), - new ColorDrawable()}); + new ActionBarColorDrawable()}); mShadowDrawable = shadow; mBackgroundDrawable = getDrawable(1); mLineDrawable = (LineBackgroundDrawable) getDrawable(2); @@ -1243,11 +1252,11 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener } else { mBackgroundDrawable.getOutline(outline); } + outline.setAlpha(mFactor * 0.99f); } @Override public void setAlpha(int alpha) { - super.setAlpha(alpha); mAlpha = alpha; setFactor(mFactor); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/graphic/ActionBarColorDrawable.java b/twidere/src/main/java/org/mariotaku/twidere/graphic/ActionBarColorDrawable.java new file mode 100644 index 000000000..0144f7077 --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/graphic/ActionBarColorDrawable.java @@ -0,0 +1,50 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2014 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.graphic; + +import android.annotation.TargetApi; +import android.graphics.Outline; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.os.Build; + +/** + * Created by mariotaku on 14/12/8. + */ +public class ActionBarColorDrawable extends ColorDrawable { + + public ActionBarColorDrawable() { + super(); + } + + public ActionBarColorDrawable(int color) { + super(color); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public void getOutline(Outline outline) { + final Rect bounds = getBounds(); + // Very very dirty hack to make outline shadow in action bar not visible beneath status bar + outline.setRect(bounds.left - bounds.width() / 2, -bounds.height(), + bounds.right + bounds.width() / 2, bounds.bottom); + outline.setAlpha(getAlpha() / 255f); + } +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/ArrayUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/ArrayUtils.java index b82e7d468..3e198a6f5 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/ArrayUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/ArrayUtils.java @@ -19,247 +19,257 @@ package org.mariotaku.twidere.util; +import android.support.annotation.NonNull; + import java.util.ArrayList; import java.util.List; public final class ArrayUtils { - private ArrayUtils() { - throw new AssertionError("You are trying to create an instance for this utility class!"); - } + private ArrayUtils() { + throw new AssertionError("You are trying to create an instance for this utility class!"); + } - public static boolean contains(final int[] array, final int value) { - if (array == null) return false; - for (final int item : array) { - if (item == value) return true; - } - return false; - } + public static boolean contains(final int[] array, final int value) { + if (array == null) return false; + for (final int item : array) { + if (item == value) return true; + } + return false; + } - public static boolean contains(final long[] array, final long value) { - if (array == null) return false; - for (final long item : array) { - if (item == value) return true; - } - return false; - } + public static boolean contains(final long[] array, final long value) { + if (array == null) return false; + for (final long item : array) { + if (item == value) return true; + } + return false; + } - public static boolean contains(final Object[] array, final Object value) { - if (array == null || value == null) return false; - return contains(array, new Object[] { value }); - } + public static boolean contains(final Object[] array, final Object value) { + if (array == null || value == null) return false; + return contains(array, new Object[]{value}); + } - public static boolean contains(final Object[] array, final Object[] values) { - if (array == null || values == null) return false; - for (final Object item : array) { - for (final Object value : values) { - if (item == null || value == null) { - if (item == value) return true; - continue; - } - if (item.equals(value)) return true; - } - } - return false; - } + public static boolean contains(final Object[] array, final Object[] values) { + if (array == null || values == null) return false; + for (final Object item : array) { + for (final Object value : values) { + if (item == null || value == null) { + if (item == value) return true; + continue; + } + if (item.equals(value)) return true; + } + } + return false; + } - public static boolean contentMatch(final Object[] array1, final Object[] array2) { - if (array1 == null || array2 == null) return array1 == array2; - if (array1.length != array2.length) return false; - final int length = array1.length; - for (int i = 0; i < length; i++) { - if (!contains(array2, array1[i])) return false; - } - return true; - } + public static boolean contentMatch(final Object[] array1, final Object[] array2) { + if (array1 == null || array2 == null) return array1 == array2; + if (array1.length != array2.length) return false; + final int length = array1.length; + for (int i = 0; i < length; i++) { + if (!contains(array2, array1[i])) return false; + } + return true; + } - public static long[] fromList(final List list) { - if (list == null) return null; - final int count = list.size(); - final long[] array = new long[count]; - for (int i = 0; i < count; i++) { - array[i] = list.get(i); - } - return array; - } + public static long[] fromList(final List list) { + if (list == null) return null; + final int count = list.size(); + final long[] array = new long[count]; + for (int i = 0; i < count; i++) { + array[i] = list.get(i); + } + return array; + } - public static int indexOf(final int[] array, final int value) { - final int length = array.length; - for (int i = 0; i < length; i++) { - if (array[i] == value) return i; - } - return -1; - } + public static int indexOf(final int[] array, final int value) { + final int length = array.length; + for (int i = 0; i < length; i++) { + if (array[i] == value) return i; + } + return -1; + } - public static int indexOf(final long[] array, final long value) { - final int length = array.length; - for (int i = 0; i < length; i++) { - if (array[i] == value) return i; - } - return -1; - } + public static int indexOf(final long[] array, final long value) { + final int length = array.length; + for (int i = 0; i < length; i++) { + if (array[i] == value) return i; + } + return -1; + } - public static int indexOf(final Object[] array, final Object value) { - final int length = array.length; - for (int i = 0; i < length; i++) { - if (array[i].equals(value)) return i; - } - return -1; - } + public static int indexOf(final Object[] array, final Object value) { + final int length = array.length; + for (int i = 0; i < length; i++) { + if (array[i].equals(value)) return i; + } + return -1; + } - public static long[] intersection(final long[] array1, final long[] array2) { - if (array1 == null || array2 == null) return new long[0]; - final List list1 = new ArrayList(); - for (final long item : array1) { - list1.add(item); - } - final List list2 = new ArrayList(); - for (final long item : array2) { - list2.add(item); - } - list1.retainAll(list2); - return fromList(list1); - } + public static long[] intersection(final long[] array1, final long[] array2) { + if (array1 == null || array2 == null) return new long[0]; + final List list1 = new ArrayList(); + for (final long item : array1) { + list1.add(item); + } + final List list2 = new ArrayList(); + for (final long item : array2) { + list2.add(item); + } + list1.retainAll(list2); + return fromList(list1); + } - public static void mergeArray(final Object[] dest, final Object[]... arrays) { - if (arrays == null || arrays.length == 0) return; - if (arrays.length == 1) { - final Object[] array = arrays[0]; - System.arraycopy(array, 0, dest, 0, array.length); - return; - } - for (int i = 0, j = arrays.length - 1; i < j; i++) { - final Object[] array1 = arrays[i], array2 = arrays[i + 1]; - System.arraycopy(array1, 0, dest, 0, array1.length); - System.arraycopy(array2, 0, dest, array1.length, array2.length); - } - } + public static void mergeArray(final Object[] dest, final Object[]... arrays) { + if (arrays == null || arrays.length == 0) return; + if (arrays.length == 1) { + final Object[] array = arrays[0]; + System.arraycopy(array, 0, dest, 0, array.length); + return; + } + for (int i = 0, j = arrays.length - 1; i < j; i++) { + final Object[] array1 = arrays[i], array2 = arrays[i + 1]; + System.arraycopy(array1, 0, dest, 0, array1.length); + System.arraycopy(array2, 0, dest, array1.length, array2.length); + } + } - public static String mergeArrayToString(final String[] array) { - if (array == null) return null; - final StringBuilder builder = new StringBuilder(); - for (final String c : array) { - builder.append(c); - } - return builder.toString(); - } + public static String mergeArrayToString(final String[] array) { + if (array == null) return null; + final StringBuilder builder = new StringBuilder(); + for (final String c : array) { + builder.append(c); + } + return builder.toString(); + } - public static long min(final long[] array) { - if (array == null || array.length == 0) throw new IllegalArgumentException(); - long min = array[0]; - for (int i = 1, j = array.length; i < j; i++) { - if (min > array[i]) { - min = array[i]; - } - } - return min; - } + public static long min(final long[] array) { + if (array == null || array.length == 0) throw new IllegalArgumentException(); + long min = array[0]; + for (int i = 1, j = array.length; i < j; i++) { + if (min > array[i]) { + min = array[i]; + } + } + return min; + } - public static long[] parseLongArray(final String string, final char token) { - if (string == null) return new long[0]; - final String[] items_string_array = string.split(String.valueOf(token)); - final ArrayList items_list = new ArrayList(); - for (final String id_string : items_string_array) { - try { - items_list.add(Long.parseLong(id_string)); - } catch (final NumberFormatException e) { - // Ignore. - } - } - final int list_size = items_list.size(); - final long[] array = new long[list_size]; - for (int i = 0; i < list_size; i++) { - array[i] = items_list.get(i); - } - return array; - } + public static long[] parseLongArray(final String string, final char token) { + if (string == null) return new long[0]; + final String[] items_string_array = string.split(String.valueOf(token)); + final ArrayList items_list = new ArrayList(); + for (final String id_string : items_string_array) { + try { + items_list.add(Long.parseLong(id_string)); + } catch (final NumberFormatException e) { + // Ignore. + } + } + final int list_size = items_list.size(); + final long[] array = new long[list_size]; + for (int i = 0; i < list_size; i++) { + array[i] = items_list.get(i); + } + return array; + } - public static int[] subArray(final int[] array, final int start, final int end) { - final int length = end - start; - if (length < 0) throw new IllegalArgumentException(); - final int[] result = new int[length]; - System.arraycopy(array, start, result, 0, length); - return result; - } + public static void reverse(@NonNull Object[] array) { + for (int i = 0; i < array.length / 2; i++) { + Object temp = array[i]; + array[i] = array[array.length - i - 1]; + array[array.length - i - 1] = temp; + } + } - public static long[] subArray(final long[] array, final int start, final int end) { - final int length = end - start; - if (length < 0) throw new IllegalArgumentException(); - final long[] result = new long[length]; - System.arraycopy(array, start, result, 0, length); - return result; - } + public static int[] subArray(final int[] array, final int start, final int end) { + final int length = end - start; + if (length < 0) throw new IllegalArgumentException(); + final int[] result = new int[length]; + System.arraycopy(array, start, result, 0, length); + return result; + } - public static Object[] subArray(final Object[] array, final int start, final int end) { - final int length = end - start; - if (length < 0) throw new IllegalArgumentException(); - final Object[] result = new Object[length]; - System.arraycopy(array, start, result, 0, length); - return result; - } + public static long[] subArray(final long[] array, final int start, final int end) { + final int length = end - start; + if (length < 0) throw new IllegalArgumentException(); + final long[] result = new long[length]; + System.arraycopy(array, start, result, 0, length); + return result; + } - public static String[] subArray(final String[] array, final int start, final int end) { - final int length = end - start; - if (length < 0) throw new IllegalArgumentException(); - final String[] result = new String[length]; - System.arraycopy(array, start, result, 0, length); - return result; - } + public static Object[] subArray(final Object[] array, final int start, final int end) { + final int length = end - start; + if (length < 0) throw new IllegalArgumentException(); + final Object[] result = new Object[length]; + System.arraycopy(array, start, result, 0, length); + return result; + } - public static String toString(final long[] array, final char token, final boolean include_space) { - final StringBuilder builder = new StringBuilder(); - final int length = array.length; - for (int i = 0; i < length; i++) { - final String id_string = String.valueOf(array[i]); - if (id_string != null) { - if (i > 0) { - builder.append(include_space ? token + " " : token); - } - builder.append(id_string); - } - } - return builder.toString(); - } + public static String[] subArray(final String[] array, final int start, final int end) { + final int length = end - start; + if (length < 0) throw new IllegalArgumentException(); + final String[] result = new String[length]; + System.arraycopy(array, start, result, 0, length); + return result; + } - public static String toString(final Object[] array, final char token, final boolean include_space) { - final StringBuilder builder = new StringBuilder(); - final int length = array.length; - for (int i = 0; i < length; i++) { - final String id_string = String.valueOf(array[i]); - if (id_string != null) { - if (i > 0) { - builder.append(include_space ? token + " " : token); - } - builder.append(id_string); - } - } - return builder.toString(); - } + public static String toString(final long[] array, final char token, final boolean include_space) { + final StringBuilder builder = new StringBuilder(); + final int length = array.length; + for (int i = 0; i < length; i++) { + final String id_string = String.valueOf(array[i]); + if (id_string != null) { + if (i > 0) { + builder.append(include_space ? token + " " : token); + } + builder.append(id_string); + } + } + return builder.toString(); + } - public static String[] toStringArray(final Object[] array) { - if (array == null) return null; - final int length = array.length; - final String[] string_array = new String[length]; - for (int i = 0; i < length; i++) { - string_array[i] = ParseUtils.parseString(array[i]); - } - return string_array; - } + public static String toString(final Object[] array, final char token, final boolean include_space) { + final StringBuilder builder = new StringBuilder(); + final int length = array.length; + for (int i = 0; i < length; i++) { + final String id_string = String.valueOf(array[i]); + if (id_string != null) { + if (i > 0) { + builder.append(include_space ? token + " " : token); + } + builder.append(id_string); + } + } + return builder.toString(); + } - public static String[] toStringArray(final String s) { - if (s == null) return null; - return s.split("(?!^)"); - } + public static String[] toStringArray(final Object[] array) { + if (array == null) return null; + final int length = array.length; + final String[] string_array = new String[length]; + for (int i = 0; i < length; i++) { + string_array[i] = ParseUtils.parseString(array[i]); + } + return string_array; + } - public static String toStringForSQL(final String[] array) { - final int size = array != null ? array.length : 0; - final StringBuilder builder = new StringBuilder(); - for (int i = 0; i < size; i++) { - if (i > 0) { - builder.append(','); - } - builder.append('?'); - } - return builder.toString(); - } + public static String[] toStringArray(final String s) { + if (s == null) return null; + return s.split("(?!^)"); + } + + public static String toStringForSQL(final String[] array) { + final int size = array != null ? array.length : 0; + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < size; i++) { + if (i > 0) { + builder.append(','); + } + builder.append('?'); + } + return builder.toString(); + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/FlymeUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/FlymeUtils.java index 5441be6fa..13087a815 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/FlymeUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/FlymeUtils.java @@ -26,43 +26,42 @@ import java.lang.reflect.Method; public final class FlymeUtils { - private static String[] SMARTBAR_SUPPORTED_DEVICES = { "mx2", "mx3" }; + private static String[] SMARTBAR_SUPPORTED_DEVICES = {"mx2", "mx3"}; - public static boolean hasSmartBar() { - try { - // Invoke Build.hasSmartBar() - final Method method = Build.class.getMethod("hasSmartBar"); - return ((Boolean) method.invoke(null)).booleanValue(); - } catch (final Exception e) { - } - // Detect by Build.DEVICE - if (isDeviceWithSmartBar(Build.DEVICE)) return true; - return false; - } + public static boolean hasSmartBar() { + try { + // Invoke Build.hasSmartBar() + final Method method = Build.class.getMethod("hasSmartBar"); + return (Boolean) method.invoke(null); + } catch (final Exception ignored) { + } + // Detect by Build.DEVICE + return isDeviceWithSmartBar(Build.DEVICE); + } - public static boolean isDeviceWithSmartBar(final String buildDevice) { - for (final String dev : SMARTBAR_SUPPORTED_DEVICES) { - if (dev.equals(buildDevice)) return true; - } - return false; - } + public static boolean isDeviceWithSmartBar(final String buildDevice) { + for (final String dev : SMARTBAR_SUPPORTED_DEVICES) { + if (dev.equals(buildDevice)) return true; + } + return false; + } - public static boolean isFlyme() { - try { - // Invoke Build.hasSmartBar() - final Method method = Build.class.getMethod("hasSmartBar"); - return method != null; - } catch (final Exception e) { - return false; - } - } + public static boolean isFlyme() { + try { + // Invoke Build.hasSmartBar() + final Method method = Build.class.getMethod("hasSmartBar"); + return method != null; + } catch (final Exception e) { + return false; + } + } - public static void setActionModeHeaderHidden(final ActionBar actionbar, final boolean hidden) { - try { - final Method method = ActionBar.class.getMethod("setActionModeHeaderHidden", new Class[] { boolean.class }); - method.invoke(actionbar, hidden); - } catch (final Exception e) { - e.printStackTrace(); - } - } + public static void setActionModeHeaderHidden(final ActionBar actionbar, final boolean hidden) { + try { + final Method method = ActionBar.class.getMethod("setActionModeHeaderHidden", new Class[]{boolean.class}); + method.invoke(actionbar, hidden); + } catch (final Exception e) { + e.printStackTrace(); + } + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java index 747270b83..da66a8220 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java @@ -53,6 +53,7 @@ import org.mariotaku.refreshnow.widget.RefreshNowProgressIndicator.IndicatorConf import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.R; import org.mariotaku.twidere.activity.iface.IThemedActivity; +import org.mariotaku.twidere.graphic.ActionBarColorDrawable; import org.mariotaku.twidere.text.ParagraphSpacingSpan; import org.mariotaku.twidere.util.menu.TwidereMenuInfo; @@ -240,7 +241,7 @@ public class ThemeUtils implements Constants { public static Drawable getActionBarBackground(final Context context, final int themeRes, final int accentColor) { if (!isDarkTheme(themeRes)) { - final ColorDrawable d = new ColorDrawable(accentColor); + final ColorDrawable d = new ActionBarColorDrawable(accentColor); return applyActionBarDrawable(context, d, isTransparentBackground(themeRes)); } final TypedArray a = context.obtainStyledAttributes(null, new int[]{android.R.attr.background}, diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/layout/LinearLayoutManager.java b/twidere/src/main/java/org/mariotaku/twidere/util/layout/LinearLayoutManager.java deleted file mode 100644 index 9ce315161..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/util/layout/LinearLayoutManager.java +++ /dev/null @@ -1,1994 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.util.layout; - -import android.content.Context; -import android.graphics.PointF; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityRecordCompat; -import android.support.v7.widget.LinearSmoothScroller; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.ViewHolderTrojan; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; - -import java.util.List; - -import static android.support.v7.widget.RecyclerView.NO_POSITION; - -/** - * A {@link android.support.v7.widget.RecyclerView.LayoutManager} implementation which provides - * similar functionality to {@link android.widget.ListView}. - */ -public class LinearLayoutManager extends RecyclerView.LayoutManager { - - private static final String TAG = "LinearLayoutManager"; - - private static final boolean DEBUG = false; - - public static final int HORIZONTAL = OrientationHelper.HORIZONTAL; - - public static final int VERTICAL = OrientationHelper.VERTICAL; - - public static final int INVALID_OFFSET = Integer.MIN_VALUE; - - - /** - * While trying to find next view to focus, LayoutManager will not try to scroll more - * than this factor times the total space of the list. If layout is vertical, total space is the - * height minus padding, if layout is horizontal, total space is the width minus padding. - */ - private static final float MAX_SCROLL_FACTOR = 0.33f; - - - /** - * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL} - */ - int mOrientation; - - /** - * Helper class that keeps temporary layout state. - * It does not keep state after layout is complete but we still keep a reference to re-use - * the same object. - */ - private LayoutState mLayoutState; - - /** - * Many calculations are made depending on orientation. To keep it clean, this interface - * helps {@link LinearLayoutManager} make those decisions. - * Based on {@link #mOrientation}, an implementation is lazily created in - * {@link #ensureLayoutState} method. - */ - OrientationHelper mOrientationHelper; - - /** - * We need to track this so that we can ignore current position when it changes. - */ - private boolean mLastStackFromEnd; - - - /** - * Defines if layout should be calculated from end to start. - * - * @see #mShouldReverseLayout - */ - private boolean mReverseLayout = false; - - /** - * This keeps the final value for how LayoutManager should start laying out views. - * It is calculated by checking {@link #getReverseLayout()} and View's layout direction. - * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} is run. - */ - boolean mShouldReverseLayout = false; - - /** - * Works the same way as {@link android.widget.AbsListView#setStackFromBottom(boolean)} and - * it supports both orientations. - * see {@link android.widget.AbsListView#setStackFromBottom(boolean)} - */ - private boolean mStackFromEnd = false; - - /** - * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}. - * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)} - */ - private boolean mSmoothScrollbarEnabled = true; - - /** - * When LayoutManager needs to scroll to a position, it sets this variable and requests a - * layout which will check this variable and re-layout accordingly. - */ - int mPendingScrollPosition = NO_POSITION; - - /** - * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is - * called. - */ - int mPendingScrollPositionOffset = INVALID_OFFSET; - - private boolean mRecycleChildrenOnDetach; - - SavedState mPendingSavedState = null; - - /** - * Re-used variable to keep anchor information on re-layout. - * Anchor position and coordinate defines the reference point for LLM while doing a layout. - */ - final AnchorInfo mAnchorInfo; - - /** - * Creates a vertical LinearLayoutManager - * - * @param context Current context, will be used to access resources. - */ - public LinearLayoutManager(Context context) { - this(context, VERTICAL, false); - } - - /** - * @param context Current context, will be used to access resources. - * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link - * #VERTICAL}. - * @param reverseLayout When set to true, layouts from end to start. - */ - public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) { - mAnchorInfo = new AnchorInfo(); - setOrientation(orientation); - setReverseLayout(reverseLayout); - } - - /** - * {@inheritDoc} - */ - @Override - public RecyclerView.LayoutParams generateDefaultLayoutParams() { - return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - } - - /** - * Returns whether LayoutManager will recycle its children when it is detached from - * RecyclerView. - * - * @return true if LayoutManager will recycle its children when it is detached from - * RecyclerView. - */ - public boolean getRecycleChildrenOnDetach() { - return mRecycleChildrenOnDetach; - } - - /** - * Set whether LayoutManager will recycle its children when it is detached from - * RecyclerView. - *

- * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set - * this flag to true so that views will be avilable to other RecyclerViews - * immediately. - *

- * Note that, setting this flag will result in a performance drop if RecyclerView - * is restored. - * - * @param recycleChildrenOnDetach Whether children should be recycled in detach or not. - */ - public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) { - mRecycleChildrenOnDetach = recycleChildrenOnDetach; - } - - @Override - public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) { - super.onDetachedFromWindow(view, recycler); - if (mRecycleChildrenOnDetach) { - removeAndRecycleAllViews(recycler); - recycler.clear(); - } - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - if (getChildCount() > 0) { - final AccessibilityRecordCompat record = AccessibilityEventCompat - .asRecord(event); - record.setFromIndex(findFirstVisibleItemPosition()); - record.setToIndex(findLastVisibleItemPosition()); - } - } - - @Override - public Parcelable onSaveInstanceState() { - if (mPendingSavedState != null) { - return new SavedState(mPendingSavedState); - } - SavedState state = new SavedState(); - if (getChildCount() > 0) { - ensureLayoutState(); - boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout; - state.mAnchorLayoutFromEnd = didLayoutFromEnd; - if (didLayoutFromEnd) { - final View refChild = getChildClosestToEnd(); - state.mAnchorOffset = mOrientationHelper.getEndAfterPadding() - - mOrientationHelper.getDecoratedEnd(refChild); - state.mAnchorPosition = getPosition(refChild); - } else { - final View refChild = getChildClosestToStart(); - state.mAnchorPosition = getPosition(refChild); - state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild) - - mOrientationHelper.getStartAfterPadding(); - } - } else { - state.invalidateAnchor(); - } - return state; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - if (state instanceof SavedState) { - mPendingSavedState = (SavedState) state; - requestLayout(); - if (DEBUG) { - Log.d(TAG, "loaded saved state"); - } - } else if (DEBUG) { - Log.d(TAG, "invalid saved state class"); - } - } - - /** - * @return true if {@link #getOrientation()} is {@link #HORIZONTAL} - */ - @Override - public boolean canScrollHorizontally() { - return mOrientation == HORIZONTAL; - } - - /** - * @return true if {@link #getOrientation()} is {@link #VERTICAL} - */ - @Override - public boolean canScrollVertically() { - return mOrientation == VERTICAL; - } - - /** - * Compatibility support for {@link android.widget.AbsListView#setStackFromBottom(boolean)} - */ - public void setStackFromEnd(boolean stackFromEnd) { - assertNotInLayoutOrScroll(null); - if (mStackFromEnd == stackFromEnd) { - return; - } - mStackFromEnd = stackFromEnd; - requestLayout(); - } - - public boolean getStackFromEnd() { - return mStackFromEnd; - } - - /** - * Returns the current orientaion of the layout. - * - * @return Current orientation. - * @see #mOrientation - * @see #setOrientation(int) - */ - public int getOrientation() { - return mOrientation; - } - - /** - * Sets the orientation of the layout. {@link android.support.v7.widget.LinearLayoutManager} - * will do its best to keep scroll position. - * - * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} - */ - public void setOrientation(int orientation) { - if (orientation != HORIZONTAL && orientation != VERTICAL) { - throw new IllegalArgumentException("invalid orientation:" + orientation); - } - assertNotInLayoutOrScroll(null); - if (orientation == mOrientation) { - return; - } - mOrientation = orientation; - mOrientationHelper = null; - requestLayout(); - } - - /** - * Calculates the view layout order. (e.g. from end to start or start to end) - * RTL layout support is applied automatically. So if layout is RTL and - * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left. - */ - private void resolveShouldLayoutReverse() { - // A == B is the same result, but we rather keep it readable - if (mOrientation == VERTICAL || !isLayoutRTL()) { - mShouldReverseLayout = mReverseLayout; - } else { - mShouldReverseLayout = !mReverseLayout; - } - } - - /** - * Returns if views are laid out from the opposite direction of the layout. - * - * @return If layout is reversed or not. - * @see {@link #setReverseLayout(boolean)} - */ - public boolean getReverseLayout() { - return mReverseLayout; - } - - /** - * Used to reverse item traversal and layout order. - * This behaves similar to the layout change for RTL views. When set to true, first item is - * laid out at the end of the UI, second item is laid out before it etc. - *

- * For horizontal layouts, it depends on the layout direction. - * When set to true, If {@link android.support.v7.widget.RecyclerView} is LTR, than it will - * layout from RTL, if {@link android.support.v7.widget.RecyclerView}} is RTL, it will layout - * from LTR. - *

- * If you are looking for the exact same behavior of - * {@link android.widget.AbsListView#setStackFromBottom(boolean)}, use - * {@link #setStackFromEnd(boolean)} - */ - public void setReverseLayout(boolean reverseLayout) { - assertNotInLayoutOrScroll(null); - if (reverseLayout == mReverseLayout) { - return; - } - mReverseLayout = reverseLayout; - requestLayout(); - } - - /** - * {@inheritDoc} - */ - @Override - public View findViewByPosition(int position) { - final int childCount = getChildCount(); - if (childCount == 0) { - return null; - } - final int firstChild = getPosition(getChildAt(0)); - final int viewPosition = position - firstChild; - if (viewPosition >= 0 && viewPosition < childCount) { - return getChildAt(viewPosition); - } - return null; - } - - /** - *

Returns the amount of extra space that should be laid out by LayoutManager. - * By default, {@link android.support.v7.widget.LinearLayoutManager} lays out 1 extra page of - * items while smooth scrolling and 0 otherwise. You can override this method to implement your - * custom layout pre-cache logic.

- *

Laying out invisible elements will eventually come with performance cost. On the other - * hand, in places like smooth scrolling to an unknown location, this extra content helps - * LayoutManager to calculate a much smoother scrolling; which improves user experience.

- *

You can also use this if you are trying to pre-layout your upcoming views.

- * - * @return The extra space that should be laid out (in pixels). - */ - protected int getExtraLayoutSpace(RecyclerView.State state) { - if (state.hasTargetScrollPosition()) { - return mOrientationHelper.getTotalSpace(); - } else { - return 0; - } - } - - @Override - public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, - int position) { - LinearSmoothScroller linearSmoothScroller = - new LinearSmoothScroller(recyclerView.getContext()) { - @Override - public PointF computeScrollVectorForPosition(int targetPosition) { - return LinearLayoutManager.this - .computeScrollVectorForPosition(targetPosition); - } - }; - linearSmoothScroller.setTargetPosition(position); - startSmoothScroll(linearSmoothScroller); - } - - public PointF computeScrollVectorForPosition(int targetPosition) { - if (getChildCount() == 0) { - return null; - } - final int firstChildPos = getPosition(getChildAt(0)); - final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1; - if (mOrientation == HORIZONTAL) { - return new PointF(direction, 0); - } else { - return new PointF(0, direction); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { - // layout algorithm: - // 1) by checking children and other variables, find an anchor coordinate and an anchor - // item position. - // 2) fill towards start, stacking from bottom - // 3) fill towards end, stacking from top - // 4) scroll to fulfill requirements like stack from bottom. - // create layout state - if (DEBUG) { - Log.d(TAG, "is pre layout:" + state.isPreLayout()); - } - if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { - mPendingScrollPosition = mPendingSavedState.mAnchorPosition; - } - - ensureLayoutState(); - mLayoutState.mRecycle = false; - // resolve layout direction - resolveShouldLayoutReverse(); - - mAnchorInfo.reset(); - mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; - // calculate anchor position and coordinate - updateAnchorInfoForLayout(state, mAnchorInfo); - if (DEBUG) { - Log.d(TAG, "Anchor info:" + mAnchorInfo); - } - - // LLM may decide to layout items for "extra" pixels to account for scrolling target, - // caching or predictive animations. - int extraForStart; - int extraForEnd; - final int extra = getExtraLayoutSpace(state); - boolean before = state.getTargetScrollPosition() < mAnchorInfo.mPosition; - if (before == mShouldReverseLayout) { - extraForEnd = extra; - extraForStart = 0; - } else { - extraForStart = extra; - extraForEnd = 0; - } - extraForStart += mOrientationHelper.getStartAfterPadding(); - extraForEnd += mOrientationHelper.getEndPadding(); - if (state.isPreLayout() && mPendingScrollPosition != NO_POSITION && - mPendingScrollPositionOffset != INVALID_OFFSET) { - // if the child is visible and we are going to move it around, we should layout - // extra items in the opposite direction to make sure new items animate nicely - // instead of just fading in - final View existing = findViewByPosition(mPendingScrollPosition); - if (existing != null) { - final int current; - final int upcomingOffset; - if (mShouldReverseLayout) { - current = mOrientationHelper.getEndAfterPadding() - - mOrientationHelper.getDecoratedEnd(existing); - upcomingOffset = current - mPendingScrollPositionOffset; - } else { - current = mOrientationHelper.getDecoratedStart(existing) - - mOrientationHelper.getStartAfterPadding(); - upcomingOffset = mPendingScrollPositionOffset - current; - } - if (upcomingOffset > 0) { - extraForStart += upcomingOffset; - } else { - extraForEnd -= upcomingOffset; - } - } - } - int startOffset; - int endOffset; - onAnchorReady(state, mAnchorInfo); - detachAndScrapAttachedViews(recycler); - mLayoutState.mIsPreLayout = state.isPreLayout(); - if (mAnchorInfo.mLayoutFromEnd) { - // fill towards start - updateLayoutStateToFillStart(mAnchorInfo); - mLayoutState.mExtra = extraForStart; - fill(recycler, mLayoutState, state, false); - startOffset = mLayoutState.mOffset; - if (mLayoutState.mAvailable > 0) { - extraForEnd += mLayoutState.mAvailable; - } - // fill towards end - updateLayoutStateToFillEnd(mAnchorInfo); - mLayoutState.mExtra = extraForEnd; - mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; - fill(recycler, mLayoutState, state, false); - endOffset = mLayoutState.mOffset; - } else { - // fill towards end - updateLayoutStateToFillEnd(mAnchorInfo); - mLayoutState.mExtra = extraForEnd; - fill(recycler, mLayoutState, state, false); - endOffset = mLayoutState.mOffset; - if (mLayoutState.mAvailable > 0) { - extraForStart += mLayoutState.mAvailable; - } - // fill towards start - updateLayoutStateToFillStart(mAnchorInfo); - mLayoutState.mExtra = extraForStart; - mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; - fill(recycler, mLayoutState, state, false); - startOffset = mLayoutState.mOffset; - } - - // changes may cause gaps on the UI, try to fix them. - // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have - // changed - if (getChildCount() > 0) { - // because layout from end may be changed by scroll to position - // we re-calculate it. - // find which side we should check for gaps. - if (mShouldReverseLayout ^ mStackFromEnd) { - int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true); - startOffset += fixOffset; - endOffset += fixOffset; - fixOffset = fixLayoutStartGap(startOffset, recycler, state, false); - startOffset += fixOffset; - endOffset += fixOffset; - } else { - int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true); - startOffset += fixOffset; - endOffset += fixOffset; - fixOffset = fixLayoutEndGap(endOffset, recycler, state, false); - startOffset += fixOffset; - endOffset += fixOffset; - } - } - layoutForPredictiveAnimations(recycler, state, startOffset, endOffset); - if (!state.isPreLayout()) { - mPendingScrollPosition = NO_POSITION; - mPendingScrollPositionOffset = INVALID_OFFSET; - mOrientationHelper.onLayoutComplete(); - } - mLastStackFromEnd = mStackFromEnd; - mPendingSavedState = null; // we don't need this anymore - if (DEBUG) { - validateChildOrder(); - } - } - - /** - * Method called when Anchor position is decided. Extending class can setup accordingly or - * even update anchor info if necessary. - * - * @param state - * @param anchorInfo Simple data structure to keep anchor point information for the next layout - */ - void onAnchorReady(RecyclerView.State state, AnchorInfo anchorInfo) { - } - - /** - * If necessary, layouts new items for predictive animations - */ - private void layoutForPredictiveAnimations(RecyclerView.Recycler recycler, - RecyclerView.State state, int startOffset, int endOffset) { - // If there are scrap children that we did not layout, we need to find where they did go - // and layout them accordingly so that animations can work as expected. - // This case may happen if new views are added or an existing view expands and pushes - // another view out of bounds. - if (!state.willRunPredictiveAnimations() || getChildCount() == 0 || state.isPreLayout() - || !supportsPredictiveItemAnimations()) { - return; - } - - // to make the logic simpler, we calculate the size of children and call fill. - int scrapExtraStart = 0, scrapExtraEnd = 0; - final List scrapList = recycler.getScrapList(); - final int scrapSize = scrapList.size(); - final int firstChildPos = getPosition(getChildAt(0)); - for (int i = 0; i < scrapSize; i++) { - RecyclerView.ViewHolder scrap = scrapList.get(i); - final int position = scrap.getPosition(); - final int direction = position < firstChildPos != mShouldReverseLayout - ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END; - if (direction == LayoutState.LAYOUT_START) { - scrapExtraStart += mOrientationHelper.getDecoratedMeasurement(scrap.itemView); - } else { - scrapExtraEnd += mOrientationHelper.getDecoratedMeasurement(scrap.itemView); - } - } - - if (DEBUG) { - Log.d(TAG, "for unused scrap, decided to add " + scrapExtraStart - + " towards start and " + scrapExtraEnd + " towards end"); - } - mLayoutState.mScrapList = scrapList; - if (scrapExtraStart > 0) { - View anchor = getChildClosestToStart(); - updateLayoutStateToFillStart(getPosition(anchor), startOffset); - mLayoutState.mExtra = scrapExtraStart; - mLayoutState.mAvailable = 0; - mLayoutState.mCurrentPosition += mShouldReverseLayout ? 1 : -1; - fill(recycler, mLayoutState, state, false); - } - - if (scrapExtraEnd > 0) { - View anchor = getChildClosestToEnd(); - updateLayoutStateToFillEnd(getPosition(anchor), endOffset); - mLayoutState.mExtra = scrapExtraEnd; - mLayoutState.mAvailable = 0; - mLayoutState.mCurrentPosition += mShouldReverseLayout ? -1 : 1; - fill(recycler, mLayoutState, state, false); - } - mLayoutState.mScrapList = null; - } - - private void updateAnchorInfoForLayout(RecyclerView.State state, AnchorInfo anchorInfo) { - if (updateAnchorFromPendingData(state, anchorInfo)) { - if (DEBUG) { - Log.d(TAG, "updated anchor info from pending information"); - } - return; - } - - if (updateAnchorFromChildren(state, anchorInfo)) { - if (DEBUG) { - Log.d(TAG, "updated anchor info from existing children"); - } - return; - } - if (DEBUG) { - Log.d(TAG, "deciding anchor info for fresh state"); - } - anchorInfo.assignCoordinateFromPadding(); - anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0; - } - - /** - * Finds an anchor child from existing Views. Most of the time, this is the view closest to - * start or end that has a valid position (e.g. not removed). - *

- * If a child has focus, it is given priority. - */ - private boolean updateAnchorFromChildren(RecyclerView.State state, AnchorInfo anchorInfo) { - if (getChildCount() == 0) { - return false; - } - View focused = getFocusedChild(); - if (focused != null && anchorInfo.assignFromViewIfValid(focused, state)) { - if (DEBUG) { - Log.d(TAG, "decided anchor child from focused view"); - } - return true; - } - - if (mLastStackFromEnd != mStackFromEnd) { - return false; - } - - View referenceChild = anchorInfo.mLayoutFromEnd ? findReferenceChildClosestToEnd(state) - : findReferenceChildClosestToStart(state); - if (referenceChild != null) { - anchorInfo.assignFromView(referenceChild); - // If all visible views are removed in 1 pass, reference child might be out of bounds. - // If that is the case, offset it back to 0 so that we use these pre-layout children. - if (!state.isPreLayout() && supportsPredictiveItemAnimations()) { - // validate this child is at least partially visible. if not, offset it to start - final boolean notVisible = - mOrientationHelper.getDecoratedStart(referenceChild) >= mOrientationHelper - .getEndAfterPadding() - || mOrientationHelper.getDecoratedEnd(referenceChild) - < mOrientationHelper.getStartAfterPadding(); - if (notVisible) { - anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd - ? mOrientationHelper.getEndAfterPadding() - : mOrientationHelper.getStartAfterPadding(); - } - } - return true; - } - return false; - } - - /** - * If there is a pending scroll position or saved states, updates the anchor info from that - * data and returns true - */ - private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) { - if (state.isPreLayout() || mPendingScrollPosition == NO_POSITION) { - return false; - } - // validate scroll position - if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) { - mPendingScrollPosition = NO_POSITION; - mPendingScrollPositionOffset = INVALID_OFFSET; - if (DEBUG) { - Log.e(TAG, "ignoring invalid scroll position " + mPendingScrollPosition); - } - return false; - } - - // if child is visible, try to make it a reference child and ensure it is fully visible. - // if child is not visible, align it depending on its virtual position. - anchorInfo.mPosition = mPendingScrollPosition; - if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { - // Anchor offset depends on how that child was laid out. Here, we update it - // according to our current view bounds - anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd; - if (anchorInfo.mLayoutFromEnd) { - anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() - - mPendingSavedState.mAnchorOffset; - } else { - anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding() + - mPendingSavedState.mAnchorOffset; - } - return true; - } - - if (mPendingScrollPositionOffset == INVALID_OFFSET) { - View child = findViewByPosition(mPendingScrollPosition); - if (child != null) { - final int childSize = mOrientationHelper.getDecoratedMeasurement(child); - if (childSize > mOrientationHelper.getTotalSpace()) { - // item does not fit. fix depending on layout direction - anchorInfo.assignCoordinateFromPadding(); - return true; - } - final int startGap = mOrientationHelper.getDecoratedStart(child) - - mOrientationHelper.getStartAfterPadding(); - if (startGap < 0) { - anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding(); - anchorInfo.mLayoutFromEnd = false; - return true; - } - final int endGap = mOrientationHelper.getEndAfterPadding() - - mOrientationHelper.getDecoratedEnd(child); - if (endGap < 0) { - anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding(); - anchorInfo.mLayoutFromEnd = true; - return true; - } - anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd - ? (mOrientationHelper.getDecoratedEnd(child) + mOrientationHelper - .getTotalSpaceChange()) - : mOrientationHelper.getDecoratedStart(child); - } else { // item is not visible. - if (getChildCount() > 0) { - // get position of any child, does not matter - int pos = getPosition(getChildAt(0)); - anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos - == mShouldReverseLayout; - } - anchorInfo.assignCoordinateFromPadding(); - } - return true; - } - // override layout from end values for consistency - anchorInfo.mLayoutFromEnd = mShouldReverseLayout; - if (mShouldReverseLayout) { - anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() - - mPendingScrollPositionOffset; - } else { - anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding() + - mPendingScrollPositionOffset; - } - return true; - } - - /** - * @return The final offset amount for children - */ - private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler, - RecyclerView.State state, boolean canOffsetChildren) { - int gap = mOrientationHelper.getEndAfterPadding() - endOffset; - int fixOffset = 0; - if (gap > 0) { - fixOffset = -scrollBy(-gap, recycler, state); - } else { - return 0; // nothing to fix - } - // move offset according to scroll amount - endOffset += fixOffset; - if (canOffsetChildren) { - // re-calculate gap, see if we could fix it - gap = mOrientationHelper.getEndAfterPadding() - endOffset; - if (gap > 0) { - mOrientationHelper.offsetChildren(gap); - return gap + fixOffset; - } - } - return fixOffset; - } - - /** - * @return The final offset amount for children - */ - private int fixLayoutStartGap(int startOffset, RecyclerView.Recycler recycler, - RecyclerView.State state, boolean canOffsetChildren) { - int gap = startOffset - mOrientationHelper.getStartAfterPadding(); - int fixOffset = 0; - if (gap > 0) { - // check if we should fix this gap. - fixOffset = -scrollBy(gap, recycler, state); - } else { - return 0; // nothing to fix - } - startOffset += fixOffset; - if (canOffsetChildren) { - // re-calculate gap, see if we could fix it - gap = startOffset - mOrientationHelper.getStartAfterPadding(); - if (gap > 0) { - mOrientationHelper.offsetChildren(-gap); - return fixOffset - gap; - } - } - return fixOffset; - } - - private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) { - updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate); - } - - private void updateLayoutStateToFillEnd(int itemPosition, int offset) { - mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset; - mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD : - LayoutState.ITEM_DIRECTION_TAIL; - mLayoutState.mCurrentPosition = itemPosition; - mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END; - mLayoutState.mOffset = offset; - mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN; - } - - private void updateLayoutStateToFillStart(AnchorInfo anchorInfo) { - updateLayoutStateToFillStart(anchorInfo.mPosition, anchorInfo.mCoordinate); - } - - private void updateLayoutStateToFillStart(int itemPosition, int offset) { - mLayoutState.mAvailable = offset - mOrientationHelper.getStartAfterPadding(); - mLayoutState.mCurrentPosition = itemPosition; - mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL : - LayoutState.ITEM_DIRECTION_HEAD; - mLayoutState.mLayoutDirection = LayoutState.LAYOUT_START; - mLayoutState.mOffset = offset; - mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN; - - } - - protected boolean isLayoutRTL() { - return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL; - } - - void ensureLayoutState() { - if (mLayoutState == null) { - mLayoutState = new LayoutState(); - } - if (mOrientationHelper == null) { - mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation); - } - } - - /** - *

Scroll the RecyclerView to make the position visible.

- *

- *

RecyclerView will scroll the minimum amount that is necessary to make the - * target position visible. If you are looking for a similar behavior to - * {@link android.widget.ListView#setSelection(int)} or - * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use - * {@link #scrollToPositionWithOffset(int, int)}.

- *

- *

Note that scroll position change will not be reflected until the next layout call.

- * - * @param position Scroll to this adapter position - * @see #scrollToPositionWithOffset(int, int) - */ - @Override - public void scrollToPosition(int position) { - mPendingScrollPosition = position; - mPendingScrollPositionOffset = INVALID_OFFSET; - if (mPendingSavedState != null) { - mPendingSavedState.invalidateAnchor(); - } - requestLayout(); - } - - /** - * Scroll to the specified adapter position with the given offset from resolved layout - * start. Resolved layout start depends on {@link #getReverseLayout()}, - * {@link ViewCompat#getLayoutDirection(android.view.View)} and {@link #getStackFromEnd()}. - *

- * For example, if layout is {@link #VERTICAL} and {@link #getStackFromEnd()} is true, calling - * scrollToPositionWithOffset(10, 20) will layout such that - * item[10]'s bottom is 20 pixels above the RecyclerView's bottom. - *

- * Note that scroll position change will not be reflected until the next layout call. - *

- *

- * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}. - * - * @param position Index (starting at 0) of the reference item. - * @param offset The distance (in pixels) between the start edge of the item view and - * start edge of the RecyclerView. - * @see #setReverseLayout(boolean) - * @see #scrollToPosition(int) - */ - public void scrollToPositionWithOffset(int position, int offset) { - mPendingScrollPosition = position; - mPendingScrollPositionOffset = offset; - if (mPendingSavedState != null) { - mPendingSavedState.invalidateAnchor(); - } - requestLayout(); - } - - - /** - * {@inheritDoc} - */ - @Override - public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, - RecyclerView.State state) { - if (mOrientation == VERTICAL) { - return 0; - } - return scrollBy(dx, recycler, state); - } - - /** - * {@inheritDoc} - */ - @Override - public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, - RecyclerView.State state) { - if (mOrientation == HORIZONTAL) { - return 0; - } - return scrollBy(dy, recycler, state); - } - - @Override - public int computeHorizontalScrollOffset(RecyclerView.State state) { - return computeScrollOffset(state); - } - - @Override - public int computeVerticalScrollOffset(RecyclerView.State state) { - return computeScrollOffset(state); - } - - @Override - public int computeHorizontalScrollExtent(RecyclerView.State state) { - return computeScrollExtent(state); - } - - @Override - public int computeVerticalScrollExtent(RecyclerView.State state) { - return computeScrollExtent(state); - } - - @Override - public int computeHorizontalScrollRange(RecyclerView.State state) { - return computeScrollRange(state); - } - - @Override - public int computeVerticalScrollRange(RecyclerView.State state) { - return computeScrollRange(state); - } - - private int computeScrollOffset(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - ensureLayoutState(); - return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper, - getChildClosestToStart(), getChildClosestToEnd(), this, - mSmoothScrollbarEnabled, mShouldReverseLayout); - } - - private int computeScrollExtent(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - ensureLayoutState(); - return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper, - getChildClosestToStart(), getChildClosestToEnd(), this, - mSmoothScrollbarEnabled); - } - - private int computeScrollRange(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - ensureLayoutState(); - return ScrollbarHelper.computeScrollRange(state, mOrientationHelper, - getChildClosestToStart(), getChildClosestToEnd(), this, - mSmoothScrollbarEnabled); - } - - /** - * When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed - * based on the number of visible pixels in the visible items. This however assumes that all - * list items have similar or equal widths or heights (depending on list orientation). - * If you use a list in which items have different dimensions, the scrollbar will change - * appearance as the user scrolls through the list. To avoid this issue, you need to disable - * this property. - *

- * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based - * solely on the number of items in the adapter and the position of the visible items inside - * the adapter. This provides a stable scrollbar as the user navigates through a list of items - * with varying widths / heights. - * - * @param enabled Whether or not to enable smooth scrollbar. - * @see #setSmoothScrollbarEnabled(boolean) - */ - public void setSmoothScrollbarEnabled(boolean enabled) { - mSmoothScrollbarEnabled = enabled; - } - - /** - * Returns the current state of the smooth scrollbar feature. It is enabled by default. - * - * @return True if smooth scrollbar is enabled, false otherwise. - * @see #setSmoothScrollbarEnabled(boolean) - */ - public boolean isSmoothScrollbarEnabled() { - return mSmoothScrollbarEnabled; - } - - private void updateLayoutState(int layoutDirection, int requiredSpace, - boolean canUseExistingSpace, RecyclerView.State state) { - mLayoutState.mExtra = getExtraLayoutSpace(state); - mLayoutState.mLayoutDirection = layoutDirection; - int fastScrollSpace; - if (layoutDirection == LayoutState.LAYOUT_END) { - mLayoutState.mExtra += mOrientationHelper.getEndPadding(); - // get the first child in the direction we are going - final View child = getChildClosestToEnd(); - // the direction in which we are traversing children - mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD - : LayoutState.ITEM_DIRECTION_TAIL; - mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection; - mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child); - // calculate how much we can scroll without adding new children (independent of layout) - fastScrollSpace = mOrientationHelper.getDecoratedEnd(child) - - mOrientationHelper.getEndAfterPadding(); - - } else { - final View child = getChildClosestToStart(); - mLayoutState.mExtra += mOrientationHelper.getStartAfterPadding(); - mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL - : LayoutState.ITEM_DIRECTION_HEAD; - mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection; - mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child); - fastScrollSpace = -mOrientationHelper.getDecoratedStart(child) - + mOrientationHelper.getStartAfterPadding(); - } - mLayoutState.mAvailable = requiredSpace; - if (canUseExistingSpace) { - mLayoutState.mAvailable -= fastScrollSpace; - } - mLayoutState.mScrollingOffset = fastScrollSpace; - } - - int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { - if (getChildCount() == 0 || dy == 0) { - return 0; - } - mLayoutState.mRecycle = true; - ensureLayoutState(); - final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; - final int absDy = Math.abs(dy); - updateLayoutState(layoutDirection, absDy, true, state); - final int freeScroll = mLayoutState.mScrollingOffset; - final int consumed = freeScroll + fill(recycler, mLayoutState, state, false); - if (consumed < 0) { - if (DEBUG) { - Log.d(TAG, "Don't have any more elements to scroll"); - } - return 0; - } - final int scrolled = absDy > consumed ? layoutDirection * consumed : dy; - mOrientationHelper.offsetChildren(-scrolled); - if (DEBUG) { - Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled); - } - return scrolled; - } - - @Override - public void assertNotInLayoutOrScroll(String message) { - if (mPendingSavedState == null) { - super.assertNotInLayoutOrScroll(message); - } - } - - /** - * Recycles children between given indices. - * - * @param startIndex inclusive - * @param endIndex exclusive - */ - private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) { - if (startIndex == endIndex) { - return; - } - if (DEBUG) { - Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items"); - } - if (endIndex > startIndex) { - for (int i = endIndex - 1; i >= startIndex; i--) { - removeAndRecycleViewAt(i, recycler); - } - } else { - for (int i = startIndex; i > endIndex; i--) { - removeAndRecycleViewAt(i, recycler); - } - } - } - - /** - * Recycles views that went out of bounds after scrolling towards the end of the layout. - * - * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView} - * @param dt This can be used to add additional padding to the visible area. This is used - * to - * detect children that will go out of bounds after scrolling, without actually - * moving them. - */ - private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) { - if (dt < 0) { - if (DEBUG) { - Log.d(TAG, "Called recycle from start with a negative value. This might happen" - + " during layout changes but may be sign of a bug"); - } - return; - } - // ignore padding, ViewGroup may not clip children. - final int limit = dt; - final int childCount = getChildCount(); - if (mShouldReverseLayout) { - for (int i = childCount - 1; i >= 0; i--) { - View child = getChildAt(i); - if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here - recycleChildren(recycler, childCount - 1, i); - return; - } - } - } else { - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here - recycleChildren(recycler, 0, i); - return; - } - } - } - } - - - /** - * Recycles views that went out of bounds after scrolling towards the start of the layout. - * - * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView} - * @param dt This can be used to add additional padding to the visible area. This is used - * to detect children that will go out of bounds after scrolling, without - * actually moving them. - */ - private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) { - final int childCount = getChildCount(); - if (dt < 0) { - if (DEBUG) { - Log.d(TAG, "Called recycle from end with a negative value. This might happen" - + " during layout changes but may be sign of a bug"); - } - return; - } - final int limit = mOrientationHelper.getEnd() - dt; - if (mShouldReverseLayout) { - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here - recycleChildren(recycler, 0, i); - return; - } - } - } else { - for (int i = childCount - 1; i >= 0; i--) { - View child = getChildAt(i); - if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here - recycleChildren(recycler, childCount - 1, i); - return; - } - } - } - - } - - /** - * Helper method to call appropriate recycle method depending on current layout direction - * - * @param recycler Current recycler that is attached to RecyclerView - * @param layoutState Current layout state. Right now, this object does not change but - * we may consider moving it out of this view so passing around as a - * parameter for now, rather than accessing {@link #mLayoutState} - * @see #recycleViewsFromStart(android.support.v7.widget.RecyclerView.Recycler, int) - * @see #recycleViewsFromEnd(android.support.v7.widget.RecyclerView.Recycler, int) - * @see android.support.v7.widget.LinearLayoutManager.LayoutState#mLayoutDirection - */ - private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) { - if (!layoutState.mRecycle) { - return; - } - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - recycleViewsFromEnd(recycler, layoutState.mScrollingOffset); - } else { - recycleViewsFromStart(recycler, layoutState.mScrollingOffset); - } - } - - /** - * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly - * independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager} - * and with little change, can be made publicly available as a helper class. - * - * @param recycler Current recycler that is attached to RecyclerView - * @param layoutState Configuration on how we should fill out the available space. - * @param state Context passed by the RecyclerView to control scroll steps. - * @param stopOnFocusable If true, filling stops in the first focusable new child - * @return Number of pixels that it added. Useful for scoll functions. - */ - int fill(RecyclerView.Recycler recycler, LayoutState layoutState, - RecyclerView.State state, boolean stopOnFocusable) { - // max offset we should set is mFastScroll + available - final int start = layoutState.mAvailable; - if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) { - // TODO ugly bug fix. should not happen - if (layoutState.mAvailable < 0) { - layoutState.mScrollingOffset += layoutState.mAvailable; - } - recycleByLayoutState(recycler, layoutState); - } - int remainingSpace = layoutState.mAvailable + layoutState.mExtra; - LayoutChunkResult layoutChunkResult = new LayoutChunkResult(); - while (remainingSpace > 0 && layoutState.hasMore(state)) { - layoutChunkResult.resetInternal(); - layoutChunk(recycler, state, layoutState, layoutChunkResult); - if (layoutChunkResult.mFinished) { - break; - } - layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; - /** - * Consume the available space if: - * * layoutChunk did not request to be ignored - * * OR we are laying out scrap children - * * OR we are not doing pre-layout - */ - if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null - || !state.isPreLayout()) { - layoutState.mAvailable -= layoutChunkResult.mConsumed; - // we keep a separate remaining space because mAvailable is important for recycling - remainingSpace -= layoutChunkResult.mConsumed; - } - - if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) { - layoutState.mScrollingOffset += layoutChunkResult.mConsumed; - if (layoutState.mAvailable < 0) { - layoutState.mScrollingOffset += layoutState.mAvailable; - } - recycleByLayoutState(recycler, layoutState); - } - if (stopOnFocusable && layoutChunkResult.mFocusable) { - break; - } - } - if (DEBUG) { - validateChildOrder(); - } - return start - layoutState.mAvailable; - } - - void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, - LayoutState layoutState, LayoutChunkResult result) { - View view = layoutState.next(recycler); - if (view == null) { - if (DEBUG && layoutState.mScrapList == null) { - throw new RuntimeException("received null view when unexpected"); - } - // if we are laying out views in scrap, this may return null which means there is - // no more items to layout. - result.mFinished = true; - return; - } - RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); - if (layoutState.mScrapList == null) { - if (mShouldReverseLayout == (layoutState.mLayoutDirection - == LayoutState.LAYOUT_START)) { - addView(view); - } else { - addView(view, 0); - } - } else { - if (mShouldReverseLayout == (layoutState.mLayoutDirection - == LayoutState.LAYOUT_START)) { - addDisappearingView(view); - } else { - addDisappearingView(view, 0); - } - } - measureChildWithMargins(view, 0, 0); - result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); - int left, top, right, bottom; - if (mOrientation == VERTICAL) { - if (isLayoutRTL()) { - right = getWidth() - getPaddingRight(); - left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); - } else { - left = getPaddingLeft(); - right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); - } - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - bottom = layoutState.mOffset; - top = layoutState.mOffset - result.mConsumed; - } else { - top = layoutState.mOffset; - bottom = layoutState.mOffset + result.mConsumed; - } - } else { - top = getPaddingTop(); - bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view); - - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - right = layoutState.mOffset; - left = layoutState.mOffset - result.mConsumed; - } else { - left = layoutState.mOffset; - right = layoutState.mOffset + result.mConsumed; - } - } - // We calculate everything with View's bounding box (which includes decor and margins) - // To calculate correct layout position, we subtract margins. - layoutDecorated(view, left + params.leftMargin, top + params.topMargin, - right - params.rightMargin, bottom - params.bottomMargin); - if (DEBUG) { - Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" - + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" - + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)); - } - // Consume the available space if the view is not removed OR changed - if (params.isItemRemoved() || params.isItemChanged()) { - result.mIgnoreConsumed = true; - } - result.mFocusable = view.isFocusable(); - } - - /** - * Converts a focusDirection to orientation. - * - * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, - * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, - * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} - * or 0 for not applicable - * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction - * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise. - */ - private int convertFocusDirectionToLayoutDirection(int focusDirection) { - switch (focusDirection) { - case View.FOCUS_BACKWARD: - return LayoutState.LAYOUT_START; - case View.FOCUS_FORWARD: - return LayoutState.LAYOUT_END; - case View.FOCUS_UP: - return mOrientation == VERTICAL ? LayoutState.LAYOUT_START - : LayoutState.INVALID_LAYOUT; - case View.FOCUS_DOWN: - return mOrientation == VERTICAL ? LayoutState.LAYOUT_END - : LayoutState.INVALID_LAYOUT; - case View.FOCUS_LEFT: - return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START - : LayoutState.INVALID_LAYOUT; - case View.FOCUS_RIGHT: - return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END - : LayoutState.INVALID_LAYOUT; - default: - if (DEBUG) { - Log.d(TAG, "Unknown focus request:" + focusDirection); - } - return LayoutState.INVALID_LAYOUT; - } - - } - - /** - * Convenience method to find the child closes to start. Caller should check it has enough - * children. - * - * @return The child closes to start of the layout from user's perspective. - */ - private View getChildClosestToStart() { - return getChildAt(mShouldReverseLayout ? getChildCount() - 1 : 0); - } - - /** - * Convenience method to find the child closes to end. Caller should check it has enough - * children. - * - * @return The child closes to end of the layout from user's perspective. - */ - private View getChildClosestToEnd() { - return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1); - } - - - /** - * Among the children that are suitable to be considered as an anchor child, returns the one - * closest to the end of the layout. - *

- * Due to ambiguous adapter updates or children being removed, some children's positions may be - * invalid. This method is a best effort to find a position within adapter bounds if possible. - *

- * It also prioritizes children that are within the visible bounds. - * - * @return A View that can be used an an anchor View. - */ - private View findReferenceChildClosestToEnd(RecyclerView.State state) { - return mShouldReverseLayout ? findFirstReferenceChild(state.getItemCount()) : - findLastReferenceChild(state.getItemCount()); - } - - /** - * Among the children that are suitable to be considered as an anchor child, returns the one - * closest to the start of the layout. - *

- * Due to ambiguous adapter updates or children being removed, some children's positions may be - * invalid. This method is a best effort to find a position within adapter bounds if possible. - *

- * It also prioritizes children that are within the visible bounds. - * - * @return A View that can be used an an anchor View. - */ - private View findReferenceChildClosestToStart(RecyclerView.State state) { - return mShouldReverseLayout ? findLastReferenceChild(state.getItemCount()) : - findFirstReferenceChild(state.getItemCount()); - } - - private View findFirstReferenceChild(int itemCount) { - return findReferenceChild(0, getChildCount(), itemCount); - } - - private View findLastReferenceChild(int itemCount) { - return findReferenceChild(getChildCount() - 1, -1, itemCount); - } - - private View findReferenceChild(int start, int end, int itemCount) { - ensureLayoutState(); - View invalidMatch = null; - View outOfBoundsMatch = null; - final int boundsStart = mOrientationHelper.getStartAfterPadding(); - final int boundsEnd = mOrientationHelper.getEndAfterPadding(); - final int diff = end > start ? 1 : -1; - for (int i = start; i != end; i += diff) { - final View view = getChildAt(i); - final int position = getPosition(view); - if (position >= 0 && position < itemCount) { - if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) { - if (invalidMatch == null) { - invalidMatch = view; // removed item, least preferred - } - } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd || - mOrientationHelper.getDecoratedEnd(view) < boundsStart) { - if (outOfBoundsMatch == null) { - outOfBoundsMatch = view; // item is not visible, less preferred - } - } else { - return view; - } - } - } - return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch; - } - - /** - * Returns the adapter position of the first visible view. - *

- * Note that, this value is not affected by layout orientation or item order traversal. - * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, - * not in the layout. - *

- * If RecyclerView has item decorators, they will be considered in calculations as well. - *

- * LayoutManager may pre-cache some views that are not necessarily visible. Those views - * are ignored in this method. - * - * @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if - * there aren't any visible items. - * @see #findFirstCompletelyVisibleItemPosition() - * @see #findLastVisibleItemPosition() - */ - public int findFirstVisibleItemPosition() { - final View child = findOneVisibleChild(0, getChildCount(), false); - return child == null ? NO_POSITION : getPosition(child); - } - - /** - * Returns the adapter position of the first fully visible view. - *

- * Note that bounds check is only performed in the current orientation. That means, if - * LayoutManager is horizontal, it will only check the view's left and right edges. - * - * @return The adapter position of the first fully visible item or - * {@link RecyclerView#NO_POSITION} if there aren't any visible items. - * @see #findFirstVisibleItemPosition() - * @see #findLastCompletelyVisibleItemPosition() - */ - public int findFirstCompletelyVisibleItemPosition() { - final View child = findOneVisibleChild(0, getChildCount(), true); - return child == null ? NO_POSITION : getPosition(child); - } - - /** - * Returns the adapter position of the last visible view. - *

- * Note that, this value is not affected by layout orientation or item order traversal. - * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, - * not in the layout. - *

- * If RecyclerView has item decorators, they will be considered in calculations as well. - *

- * LayoutManager may pre-cache some views that are not necessarily visible. Those views - * are ignored in this method. - * - * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if - * there aren't any visible items. - * @see #findLastCompletelyVisibleItemPosition() - * @see #findFirstVisibleItemPosition() - */ - public int findLastVisibleItemPosition() { - final View child = findOneVisibleChild(getChildCount() - 1, -1, false); - return child == null ? NO_POSITION : getPosition(child); - } - - /** - * Returns the adapter position of the last fully visible view. - *

- * Note that bounds check is only performed in the current orientation. That means, if - * LayoutManager is horizontal, it will only check the view's left and right edges. - * - * @return The adapter position of the last fully visible view or - * {@link RecyclerView#NO_POSITION} if there aren't any visible items. - * @see #findLastVisibleItemPosition() - * @see #findFirstCompletelyVisibleItemPosition() - */ - public int findLastCompletelyVisibleItemPosition() { - final View child = findOneVisibleChild(getChildCount() - 1, -1, true); - return child == null ? NO_POSITION : getPosition(child); - } - - View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) { - ensureLayoutState(); - final int start = mOrientationHelper.getStartAfterPadding(); - final int end = mOrientationHelper.getEndAfterPadding(); - final int next = toIndex > fromIndex ? 1 : -1; - for (int i = fromIndex; i != toIndex; i += next) { - final View child = getChildAt(i); - final int childStart = mOrientationHelper.getDecoratedStart(child); - final int childEnd = mOrientationHelper.getDecoratedEnd(child); - if (childStart < end && childEnd > start) { - if (completelyVisible) { - if (childStart >= start && childEnd <= end) { - return child; - } - } else { - return child; - } - } - } - return null; - } - - @Override - public View onFocusSearchFailed(View focused, int focusDirection, - RecyclerView.Recycler recycler, RecyclerView.State state) { - resolveShouldLayoutReverse(); - if (getChildCount() == 0) { - return null; - } - - final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection); - if (layoutDir == LayoutState.INVALID_LAYOUT) { - return null; - } - ensureLayoutState(); - final View referenceChild; - if (layoutDir == LayoutState.LAYOUT_START) { - referenceChild = findReferenceChildClosestToStart(state); - } else { - referenceChild = findReferenceChildClosestToEnd(state); - } - if (referenceChild == null) { - if (DEBUG) { - Log.d(TAG, - "Cannot find a child with a valid position to be used for focus search."); - } - return null; - } - ensureLayoutState(); - final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace()); - updateLayoutState(layoutDir, maxScroll, false, state); - mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN; - mLayoutState.mRecycle = false; - fill(recycler, mLayoutState, state, true); - final View nextFocus; - if (layoutDir == LayoutState.LAYOUT_START) { - nextFocus = getChildClosestToStart(); - } else { - nextFocus = getChildClosestToEnd(); - } - if (nextFocus == referenceChild || !nextFocus.isFocusable()) { - return null; - } - return nextFocus; - } - - /** - * Used for debugging. - * Logs the internal representation of children to default logger. - */ - private void logChildren() { - Log.d(TAG, "internal representation of views on the screen"); - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - Log.d(TAG, "item " + getPosition(child) + ", coord:" - + mOrientationHelper.getDecoratedStart(child)); - } - Log.d(TAG, "=============="); - } - - /** - * Used for debugging. - * Validates that child views are laid out in correct order. This is important because rest of - * the algorithm relies on this constraint. - *

- * In default layout, child 0 should be closest to screen position 0 and last child should be - * closest to position WIDTH or HEIGHT. - * In reverse layout, last child should be closes to screen position 0 and first child should - * be closest to position WIDTH or HEIGHT - */ - void validateChildOrder() { - Log.d(TAG, "validating child count " + getChildCount()); - if (getChildCount() < 1) { - return; - } - int lastPos = getPosition(getChildAt(0)); - int lastScreenLoc = mOrientationHelper.getDecoratedStart(getChildAt(0)); - if (mShouldReverseLayout) { - for (int i = 1; i < getChildCount(); i++) { - View child = getChildAt(i); - int pos = getPosition(child); - int screenLoc = mOrientationHelper.getDecoratedStart(child); - if (pos < lastPos) { - logChildren(); - throw new RuntimeException("detected invalid position. loc invalid? " + - (screenLoc < lastScreenLoc)); - } - if (screenLoc > lastScreenLoc) { - logChildren(); - throw new RuntimeException("detected invalid location"); - } - } - } else { - for (int i = 1; i < getChildCount(); i++) { - View child = getChildAt(i); - int pos = getPosition(child); - int screenLoc = mOrientationHelper.getDecoratedStart(child); - if (pos < lastPos) { - logChildren(); - throw new RuntimeException("detected invalid position. loc invalid? " + - (screenLoc < lastScreenLoc)); - } - if (screenLoc < lastScreenLoc) { - logChildren(); - throw new RuntimeException("detected invalid location"); - } - } - } - } - - @Override - public boolean supportsPredictiveItemAnimations() { - return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd; - } - - /** - * Helper class that keeps temporary state while {LayoutManager} is filling out the empty - * space. - */ - static class LayoutState { - - final static String TAG = "LinearLayoutManager#LayoutState"; - - final static int LAYOUT_START = -1; - - final static int LAYOUT_END = 1; - - final static int INVALID_LAYOUT = Integer.MIN_VALUE; - - final static int ITEM_DIRECTION_HEAD = -1; - - final static int ITEM_DIRECTION_TAIL = 1; - - final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE; - - /** - * We may not want to recycle children in some cases (e.g. layout) - */ - boolean mRecycle = true; - - /** - * Pixel offset where layout should start - */ - int mOffset; - - /** - * Number of pixels that we should fill, in the layout direction. - */ - int mAvailable; - - /** - * Current position on the adapter to get the next item. - */ - int mCurrentPosition; - - /** - * Defines the direction in which the data adapter is traversed. - * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL} - */ - int mItemDirection; - - /** - * Defines the direction in which the layout is filled. - * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END} - */ - int mLayoutDirection; - - /** - * Used when LayoutState is constructed in a scrolling state. - * It should be set the amount of scrolling we can make without creating a new view. - * Settings this is required for efficient view recycling. - */ - int mScrollingOffset; - - /** - * Used if you want to pre-layout items that are not yet visible. - * The difference with {@link #mAvailable} is that, when recycling, distance laid out for - * {@link #mExtra} is not considered to avoid recycling visible children. - */ - int mExtra = 0; - - /** - * Equal to {@link RecyclerView.State#isPreLayout()}. When consuming scrap, if this value - * is set to true, we skip removed views since they should not be laid out in post layout - * step. - */ - boolean mIsPreLayout = false; - - /** - * When LLM needs to layout particular views, it sets this list in which case, LayoutState - * will only return views from this list and return null if it cannot find an item. - */ - List mScrapList = null; - - /** - * @return true if there are more items in the data adapter - */ - boolean hasMore(RecyclerView.State state) { - return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount(); - } - - /** - * Gets the view for the next element that we should layout. - * Also updates current item index to the next item, based on {@link #mItemDirection} - * - * @return The next element that we should layout. - */ - View next(RecyclerView.Recycler recycler) { - if (mScrapList != null) { - return nextFromLimitedList(); - } - final View view = recycler.getViewForPosition(mCurrentPosition); - mCurrentPosition += mItemDirection; - return view; - } - - /** - * Returns next item from limited list. - *

- * Upon finding a valid VH, sets current item position to VH.itemPosition + mItemDirection - * - * @return View if an item in the current position or direction exists if not null. - */ - private View nextFromLimitedList() { - int size = mScrapList.size(); - RecyclerView.ViewHolder closest = null; - int closestDistance = Integer.MAX_VALUE; - for (int i = 0; i < size; i++) { - RecyclerView.ViewHolder viewHolder = mScrapList.get(i); - if (!mIsPreLayout && ViewHolderTrojan.isRemoved(viewHolder)) { - continue; - } - final int distance = (viewHolder.getPosition() - mCurrentPosition) * mItemDirection; - if (distance < 0) { - continue; // item is not in current direction - } - if (distance < closestDistance) { - closest = viewHolder; - closestDistance = distance; - if (distance == 0) { - break; - } - } - } - if (DEBUG) { - Log.d(TAG, "layout from scrap. found view:?" + (closest != null)); - } - if (closest != null) { - mCurrentPosition = closest.getPosition() + mItemDirection; - return closest.itemView; - } - return null; - } - - void log() { - Log.d(TAG, "avail:" + mAvailable + ", ind:" + mCurrentPosition + ", dir:" + - mItemDirection + ", offset:" + mOffset + ", layoutDir:" + mLayoutDirection); - } - } - - static class SavedState implements Parcelable { - - int mAnchorPosition; - - int mAnchorOffset; - - boolean mAnchorLayoutFromEnd; - - public SavedState() { - - } - - SavedState(Parcel in) { - mAnchorPosition = in.readInt(); - mAnchorOffset = in.readInt(); - mAnchorLayoutFromEnd = in.readInt() == 1; - } - - public SavedState(SavedState other) { - mAnchorPosition = other.mAnchorPosition; - mAnchorOffset = other.mAnchorOffset; - mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd; - } - - boolean hasValidAnchor() { - return mAnchorPosition >= 0; - } - - void invalidateAnchor() { - mAnchorPosition = NO_POSITION; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mAnchorPosition); - dest.writeInt(mAnchorOffset); - dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - /** - * Simple data class to keep Anchor information - */ - class AnchorInfo { - int mPosition; - int mCoordinate; - boolean mLayoutFromEnd; - - void reset() { - mPosition = NO_POSITION; - mCoordinate = INVALID_OFFSET; - mLayoutFromEnd = false; - } - - /** - * assigns anchor coordinate from the RecyclerView's padding depending on current - * layoutFromEnd value - */ - void assignCoordinateFromPadding() { - mCoordinate = mLayoutFromEnd - ? mOrientationHelper.getEndAfterPadding() - : mOrientationHelper.getStartAfterPadding(); - } - - @Override - public String toString() { - return "AnchorInfo{" + - "mPosition=" + mPosition + - ", mCoordinate=" + mCoordinate + - ", mLayoutFromEnd=" + mLayoutFromEnd + - '}'; - } - - /** - * Assign anchor position information from the provided view if it is valid as a reference - * child. - */ - public boolean assignFromViewIfValid(View child, RecyclerView.State state) { - RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); - if (!lp.isItemRemoved() && lp.getViewPosition() >= 0 - && lp.getViewPosition() < state.getItemCount()) { - assignFromView(child); - return true; - } - return false; - } - - public void assignFromView(View child) { - if (mLayoutFromEnd) { - mCoordinate = mOrientationHelper.getDecoratedEnd(child) + - mOrientationHelper.getTotalSpaceChange(); - } else { - mCoordinate = mOrientationHelper.getDecoratedStart(child); - } - - mPosition = getPosition(child); - } - } - - protected static class LayoutChunkResult { - public int mConsumed; - public boolean mFinished; - public boolean mIgnoreConsumed; - public boolean mFocusable; - - void resetInternal() { - mConsumed = 0; - mFinished = false; - mIgnoreConsumed = false; - mFocusable = false; - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/layout/OrientationHelper.java b/twidere/src/main/java/org/mariotaku/twidere/util/layout/OrientationHelper.java deleted file mode 100644 index cca62651d..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/util/layout/OrientationHelper.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.util.layout; - -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.LinearLayout; - -/** - * Helper class for LayoutManagers to abstract measurements depending on the View's orientation. - *

- * It is developed to easily support vertical and horizontal orientations in a LayoutManager but - * can also be used to abstract calls around view bounds and child measurements with margins and - * decorations. - * - * @see #createHorizontalHelper(RecyclerView.LayoutManager) - * @see #createVerticalHelper(RecyclerView.LayoutManager) - */ -public abstract class OrientationHelper { - - private static final int INVALID_SIZE = Integer.MIN_VALUE; - - protected final RecyclerView.LayoutManager mLayoutManager; - - public static final int HORIZONTAL = LinearLayout.HORIZONTAL; - - public static final int VERTICAL = LinearLayout.VERTICAL; - - private int mLastTotalSpace = INVALID_SIZE; - - private OrientationHelper(RecyclerView.LayoutManager layoutManager) { - mLayoutManager = layoutManager; - } - - /** - * Call this method after onLayout method is complete if state is NOT pre-layout. - * This method records information like layout bounds that might be useful in the next layout - * calculations. - */ - public void onLayoutComplete() { - mLastTotalSpace = getTotalSpace(); - } - - /** - * Returns the layout space change between the previous layout pass and current layout pass. - *

- * Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's - * {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler, - * RecyclerView.State)} method. - * - * @return The difference between the current total space and previous layout's total space. - * @see #onLayoutComplete() - */ - public int getTotalSpaceChange() { - return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace; - } - - /** - * Returns the start of the view including its decoration and margin. - *

- * For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left - * decoration and 3px left margin, returned value will be 15px. - * - * @param view The view element to check - * @return The first pixel of the element - * @see #getDecoratedEnd(android.view.View) - */ - public abstract int getDecoratedStart(View view); - - /** - * Returns the end of the view including its decoration and margin. - *

- * For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right - * decoration and 3px right margin, returned value will be 205. - * - * @param view The view element to check - * @return The last pixel of the element - * @see #getDecoratedStart(android.view.View) - */ - public abstract int getDecoratedEnd(View view); - - /** - * Returns the space occupied by this View in the current orientation including decorations and - * margins. - * - * @param view The view element to check - * @return Total space occupied by this view - * @see #getDecoratedMeasurementInOther(View) - */ - public abstract int getDecoratedMeasurement(View view); - - /** - * Returns the space occupied by this View in the perpendicular orientation including - * decorations and margins. - * - * @param view The view element to check - * @return Total space occupied by this view in the perpendicular orientation to current one - * @see #getDecoratedMeasurement(View) - */ - public abstract int getDecoratedMeasurementInOther(View view); - - /** - * Returns the start position of the layout after the start padding is added. - * - * @return The very first pixel we can draw. - */ - public abstract int getStartAfterPadding(); - - /** - * Returns the end position of the layout after the end padding is removed. - * - * @return The end boundary for this layout. - */ - public abstract int getEndAfterPadding(); - - /** - * Returns the end position of the layout without taking padding into account. - * - * @return The end boundary for this layout without considering padding. - */ - public abstract int getEnd(); - - /** - * Offsets all children's positions by the given amount. - * - * @param amount Value to add to each child's layout parameters - */ - public abstract void offsetChildren(int amount); - - /** - * Returns the total space to layout. This number is the difference between - * {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}. - * - * @return Total space to layout children - */ - public abstract int getTotalSpace(); - - /** - * Offsets the child in this orientation. - * - * @param view View to offset - * @param offset offset amount - */ - public abstract void offsetChild(View view, int offset); - - /** - * Returns the padding at the end of the layout. For horizontal helper, this is the right - * padding and for vertical helper, this is the bottom padding. This method does not check - * whether the layout is RTL or not. - * - * @return The padding at the end of the layout. - */ - public abstract int getEndPadding(); - - /** - * Creates an OrientationHelper for the given LayoutManager and orientation. - * - * @param layoutManager LayoutManager to attach to - * @param orientation Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL} - * @return A new OrientationHelper - */ - public static OrientationHelper createOrientationHelper( - RecyclerView.LayoutManager layoutManager, int orientation) { - switch (orientation) { - case HORIZONTAL: - return createHorizontalHelper(layoutManager); - case VERTICAL: - return createVerticalHelper(layoutManager); - } - throw new IllegalArgumentException("invalid orientation"); - } - - /** - * Creates a horizontal OrientationHelper for the given LayoutManager. - * - * @param layoutManager The LayoutManager to attach to. - * @return A new OrientationHelper - */ - public static OrientationHelper createHorizontalHelper( - RecyclerView.LayoutManager layoutManager) { - return new OrientationHelper(layoutManager) { - @Override - public int getEndAfterPadding() { - return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight(); - } - - @Override - public int getEnd() { - return mLayoutManager.getWidth(); - } - - @Override - public void offsetChildren(int amount) { - mLayoutManager.offsetChildrenHorizontal(amount); - } - - @Override - public int getStartAfterPadding() { - return mLayoutManager.getPaddingLeft(); - } - - @Override - public int getDecoratedMeasurement(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin - + params.rightMargin; - } - - @Override - public int getDecoratedMeasurementInOther(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin - + params.bottomMargin; - } - - @Override - public int getDecoratedEnd(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedRight(view) + params.rightMargin; - } - - @Override - public int getDecoratedStart(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedLeft(view) - params.leftMargin; - } - - @Override - public int getTotalSpace() { - return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft() - - mLayoutManager.getPaddingRight(); - } - - @Override - public void offsetChild(View view, int offset) { - view.offsetLeftAndRight(offset); - } - - @Override - public int getEndPadding() { - return mLayoutManager.getPaddingRight(); - } - }; - } - - /** - * Creates a vertical OrientationHelper for the given LayoutManager. - * - * @param layoutManager The LayoutManager to attach to. - * @return A new OrientationHelper - */ - public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) { - return new OrientationHelper(layoutManager) { - - int diff; - - @Override - public int getEndAfterPadding() { - int heightSum = 0; - for (int i = 0, j = mLayoutManager.getChildCount(); i < j; i++) { - final View child = mLayoutManager.getChildAt(i); - if (mLayoutManager.getItemViewType(child) == 0 || heightSum != 0) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - child.getLayoutParams(); - heightSum += mLayoutManager.getDecoratedMeasuredHeight(child) - + params.topMargin + params.bottomMargin; - } - } - final int normalEnd = mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom(); - if (heightSum == 0) { - diff = 0; - return normalEnd; - } - diff = (mLayoutManager.getPaddingTop() + heightSum) - normalEnd; -// return normalEnd; - return Math.min(mLayoutManager.getPaddingTop() + heightSum, normalEnd); - } - - @Override - public int getEnd() { - return mLayoutManager.getHeight() + Math.max(0, diff); - } - - @Override - public void offsetChildren(int amount) { - mLayoutManager.offsetChildrenVertical(amount); - } - - @Override - public int getStartAfterPadding() { - return mLayoutManager.getPaddingTop(); - } - - @Override - public int getDecoratedMeasurement(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin - + params.bottomMargin; - } - - @Override - public int getDecoratedMeasurementInOther(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin - + params.rightMargin; - } - - @Override - public int getDecoratedEnd(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin; - } - - @Override - public int getDecoratedStart(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedTop(view) - params.topMargin; - } - - @Override - public int getTotalSpace() { - return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop() - - mLayoutManager.getPaddingBottom(); - } - - @Override - public void offsetChild(View view, int offset) { - view.offsetTopAndBottom(offset); - mLayoutManager.requestLayout(); - } - - @Override - public int getEndPadding() { - return mLayoutManager.getPaddingBottom(); - } - }; - } -} \ No newline at end of file diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/layout/ScrollbarHelper.java b/twidere/src/main/java/org/mariotaku/twidere/util/layout/ScrollbarHelper.java deleted file mode 100644 index 251b7e8d6..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/util/layout/ScrollbarHelper.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.util.layout; - -import android.support.v7.widget.RecyclerView; -import android.view.View; - -/** - * A helper class to do scroll offset calculations. - */ -class ScrollbarHelper { - - /** - * @param startChild View closest to start of the list. (top or left) - * @param endChild View closest to end of the list (bottom or right) - */ - static int computeScrollOffset(RecyclerView.State state, OrientationHelper orientation, - View startChild, View endChild, RecyclerView.LayoutManager lm, - boolean smoothScrollbarEnabled, boolean reverseLayout) { - if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null || - endChild == null) { - return 0; - } - final int minPosition = Math.min(lm.getPosition(startChild), lm.getPosition(endChild)); - final int maxPosition = Math.max(lm.getPosition(startChild), lm.getPosition(endChild)); - final int itemsBefore = reverseLayout - ? Math.max(0, state.getItemCount() - maxPosition - 1) - : Math.max(0, minPosition); - if (!smoothScrollbarEnabled) { - return itemsBefore; - } - final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild) - - orientation.getDecoratedStart(startChild)); - final int itemRange = Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1; - final float avgSizePerRow = (float) laidOutArea / itemRange; - - return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding() - - orientation.getDecoratedStart(startChild))); - } - - /** - * @param startChild View closest to start of the list. (top or left) - * @param endChild View closest to end of the list (bottom or right) - */ - static int computeScrollExtent(RecyclerView.State state, OrientationHelper orientation, - View startChild, View endChild, RecyclerView.LayoutManager lm, - boolean smoothScrollbarEnabled) { - if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null || - endChild == null) { - return 0; - } - if (!smoothScrollbarEnabled) { - return Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1; - } - final int extend = orientation.getDecoratedEnd(endChild) - - orientation.getDecoratedStart(startChild); - return Math.min(orientation.getTotalSpace(), extend); - } - - /** - * @param startChild View closest to start of the list. (top or left) - * @param endChild View closest to end of the list (bottom or right) - */ - static int computeScrollRange(RecyclerView.State state, OrientationHelper orientation, - View startChild, View endChild, RecyclerView.LayoutManager lm, - boolean smoothScrollbarEnabled) { - if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null || - endChild == null) { - return 0; - } - if (!smoothScrollbarEnabled) { - return state.getItemCount(); - } - // smooth scrollbar enabled. try to estimate better. - final int laidOutArea = orientation.getDecoratedEnd(endChild) - - orientation.getDecoratedStart(startChild); - final int laidOutRange = Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) - + 1; - // estimate a size for full list. - return (int) ((float) laidOutArea / laidOutRange * state.getItemCount()); - } -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/ComposeSelectAccountButton.java b/twidere/src/main/java/org/mariotaku/twidere/view/ComposeSelectAccountButton.java new file mode 100644 index 000000000..c0cb427f5 --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/view/ComposeSelectAccountButton.java @@ -0,0 +1,249 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2014 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.support.annotation.NonNull; +import android.support.v4.view.ViewCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.Adapter; +import android.support.v7.widget.RecyclerView.Recycler; +import android.support.v7.widget.RecyclerView.State; +import android.support.v7.widget.RecyclerView.ViewHolder; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import org.mariotaku.twidere.R; +import org.mariotaku.twidere.app.TwidereApplication; +import org.mariotaku.twidere.model.ParcelableAccount; +import org.mariotaku.twidere.util.ImageLoaderWrapper; +import org.mariotaku.twidere.view.iface.IColorLabelView.Helper; + +/** + * Created by mariotaku on 14/12/8. + */ +public class ComposeSelectAccountButton extends ViewGroup { + private final AccountIconsAdapter mAccountIconsAdapter; + private final Helper mColorLabelHelper; + + private OnClickListener mOnClickListener; + private OnLongClickListener mOnLongClickListener; + + public ComposeSelectAccountButton(Context context) { + this(context, null); + } + + public ComposeSelectAccountButton(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ComposeSelectAccountButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mColorLabelHelper = new Helper(this, context, attrs, defStyle); + mColorLabelHelper.setIgnorePaddings(true); + mAccountIconsAdapter = new AccountIconsAdapter(context); + final RecyclerView recyclerView = new InternalRecyclerView(context); + final LinearLayoutManager linearLayoutManager = new MyLinearLayoutManager(context); + linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); +// linearLayoutManager.setReverseLayout(true); + recyclerView.setLayoutManager(linearLayoutManager); + recyclerView.setAdapter(mAccountIconsAdapter); + ViewCompat.setOverScrollMode(recyclerView, ViewCompat.OVER_SCROLL_NEVER); + addView(recyclerView); + } + + + @Override + protected void dispatchDraw(@NonNull final Canvas canvas) { + mColorLabelHelper.dispatchDrawBackground(canvas); + super.dispatchDraw(canvas); + mColorLabelHelper.dispatchDrawLabels(canvas); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + measureChildren(widthMeasureSpec, heightMeasureSpec); + int maxWidth = 0; + for (int i = 0, j = getChildCount(); i < j; i++) { + final View child = getChildAt(i); + maxWidth = Math.max(maxWidth, child.getMeasuredWidth()); + } + if (maxWidth == 0) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } else { + setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), heightMeasureSpec); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + for (int i = 0, j = getChildCount(); i < j; i++) { + final View child = getChildAt(i); + child.layout(getPaddingLeft(), getPaddingTop(), r - l - getPaddingRight(), + b - t - getPaddingBottom()); + } + } + + static class AccountIconViewHolder extends ViewHolder { + + private final ImageView iconView; + + public AccountIconViewHolder(View itemView) { + super(itemView); + iconView = (ImageView) itemView.findViewById(android.R.id.icon); + } + + public void showAccount(AccountIconsAdapter adapter, ParcelableAccount account) { + final ImageLoaderWrapper loader = adapter.getImageLoader(); + loader.displayProfileImage(iconView, account.profile_image_url); + } + } + + public void setSelectedAccounts(long[] accountIds) { + final ParcelableAccount[] accounts = ParcelableAccount.getAccounts(getContext(), accountIds); + if (accounts != null) { + final int[] colors = new int[accounts.length]; + for (int i = 0, j = colors.length; i < j; i++) { + colors[i] = accounts[i].color; + } + mColorLabelHelper.drawEnd(colors); + } else { + mColorLabelHelper.drawEnd(null); + } + mAccountIconsAdapter.setSelectedAccounts(accounts); + } + + private static class AccountIconsAdapter extends Adapter { + private final Context mContext; + private final LayoutInflater mInflater; + private final ImageLoaderWrapper mImageLoader; + private ParcelableAccount[] mAccounts; + + public ImageLoaderWrapper getImageLoader() { + return mImageLoader; + } + + @Override + public AccountIconViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + final View view = mInflater.inflate(R.layout.adapter_item_compose_account, parent, false); + return new AccountIconViewHolder(view); + } + + @Override + public void onBindViewHolder(AccountIconViewHolder holder, int position) { + holder.showAccount(this, mAccounts[position]); + } + + @Override + public int getItemCount() { + return mAccounts != null ? mAccounts.length : 0; + } + + public void setSelectedAccounts(ParcelableAccount[] accounts) { + if (accounts != null) { +// ArrayUtils.reverse(accounts); + } + mAccounts = accounts; + notifyDataSetChanged(); + } + + public AccountIconsAdapter(Context context) { + mContext = context; + mInflater = LayoutInflater.from(context); + mImageLoader = TwidereApplication.getInstance(context).getImageLoaderWrapper(); + } + } + + private static class MyLinearLayoutManager extends LinearLayoutManager { + private int mWidth, mHeight; + + public MyLinearLayoutManager(Context context) { + super(context); + } + + @Override + public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { + final RecyclerView.LayoutParams layoutParams = super.generateLayoutParams(c, attrs); + return layoutParams; + } + + @Override + public void onLayoutChildren(Recycler recycler, State state) { + state.getItemCount(); + super.onLayoutChildren(recycler, state); + } + + private int findChildIndex(View view) { + for (int i = 0, j = getChildCount(); i < j; i++) { + if (getChildAt(i) == view) return i; + } + return -1; + } + + @Override + public void measureChildWithMargins(View child, int widthUsed, int heightUsed) { + final RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); + final int contentWidth = mWidth - getPaddingLeft() - getPaddingRight(); + final int contentHeight = mHeight - getPaddingTop() - getPaddingBottom(); + final int itemCount = getItemCount(); + final int firstVisibleItem = findFirstVisibleItemPosition(); + final int idx = findChildIndex(child); + if (firstVisibleItem < 1 && idx == 0) { + // when firstVisibleItem is 0 or -1, assume view with idx == 0 is first view + layoutParams.leftMargin = 0; + } else { + layoutParams.leftMargin = -Math.min((contentHeight * itemCount - contentWidth) + / (itemCount - 1) + child.getPaddingLeft() + child.getPaddingRight(), contentHeight); + } + super.measureChildWithMargins(child, widthUsed, heightUsed); + } + + @Override + public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) { + final int height = MeasureSpec.getSize(heightSpec), width = Math.round(height * 1.5f); + mWidth = width; + mHeight = height; + super.onMeasure(recycler, state, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), heightSpec); + } + } + + private static class InternalRecyclerView extends RecyclerView { + public InternalRecyclerView(Context context) { + super(context); + setChildrenDrawingOrderEnabled(true); + } + + @Override + protected int getChildDrawingOrder(int childCount, int i) { + return childCount - i - 1; + } + + @Override + public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { + return false; + } + } +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/HomeSlidingMenu.java b/twidere/src/main/java/org/mariotaku/twidere/view/HomeSlidingMenu.java index bc2f580e2..16f6beb7b 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/HomeSlidingMenu.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/HomeSlidingMenu.java @@ -35,7 +35,6 @@ public class HomeSlidingMenu extends SlidingMenu implements Constants { mActivity = (HomeActivity) context; } - @Override public boolean dispatchTouchEvent(@NonNull final MotionEvent ev) { switch (ev.getActionMasked()) { diff --git a/twidere/src/main/res/layout/action_item_compose_account.xml b/twidere/src/main/res/layout/action_item_compose_account.xml index e10ff4a3e..e1d68bfca 100644 --- a/twidere/src/main/res/layout/action_item_compose_account.xml +++ b/twidere/src/main/res/layout/action_item_compose_account.xml @@ -1,18 +1,9 @@ - - - - - \ No newline at end of file + android:layout_weight="0" + android:padding="@dimen/element_spacing_small"/> \ No newline at end of file diff --git a/twidere/src/main/res/layout/activity_home.xml b/twidere/src/main/res/layout/activity_home.xml index 44eef2f26..5e164c065 100644 --- a/twidere/src/main/res/layout/activity_home.xml +++ b/twidere/src/main/res/layout/activity_home.xml @@ -6,6 +6,5 @@ android:id="@+id/home_menu" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true" app:viewAbove="@layout/activity_home_content"/> \ No newline at end of file diff --git a/twidere/src/main/res/layout/adapter_item_compose_account.xml b/twidere/src/main/res/layout/adapter_item_compose_account.xml index 1d7d4f497..2c4f2484c 100644 --- a/twidere/src/main/res/layout/adapter_item_compose_account.xml +++ b/twidere/src/main/res/layout/adapter_item_compose_account.xml @@ -1,9 +1,28 @@ + + + android:padding="@dimen/element_spacing_small"> + + + + + + + diff --git a/twidere/src/main/res/values/dimens.xml b/twidere/src/main/res/values/dimens.xml index f4d529240..4b624b979 100644 --- a/twidere/src/main/res/values/dimens.xml +++ b/twidere/src/main/res/values/dimens.xml @@ -18,6 +18,7 @@ -6dp -8dp -16dp + -24dp 56dp 42dp 48dp