improved user fragment gesture
This commit is contained in:
parent
0ec9096a9d
commit
ffec110336
|
@ -25,9 +25,6 @@ import org.mariotaku.restfu.annotation.param.Param;
|
|||
import org.mariotaku.restfu.http.BodyType;
|
||||
import org.mariotaku.restfu.http.mime.Body;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/4/17.
|
||||
*/
|
||||
|
||||
public interface MediaResources {
|
||||
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package android.support.v7.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/3/24.
|
||||
*/
|
||||
public class FixedLinearLayoutManager extends LinearLayoutManager {
|
||||
|
||||
public FixedLinearLayoutManager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public FixedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
|
||||
super(context, orientation, reverseLayout);
|
||||
}
|
||||
|
||||
@Override
|
||||
View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible, boolean acceptPartiallyVisible) {
|
||||
// XXX Fixed NPE by add a simple check to child view count
|
||||
if (getChildCount() < 0) return null;
|
||||
return super.findOneVisibleChild(fromIndex, toIndex, completelyVisible, acceptPartiallyVisible);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import org.mariotaku.twidere.view.HeaderDrawerLayout.DrawerCallback;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 14/12/2.
|
||||
*/
|
||||
public class SimpleDrawerCallback implements DrawerCallback {
|
||||
|
||||
private final RecyclerView mRecyclerView;
|
||||
|
||||
public SimpleDrawerCallback(RecyclerView recyclerView) {
|
||||
mRecyclerView = recyclerView;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void fling(float velocity) {
|
||||
mRecyclerView.fling(0, (int) velocity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollBy(float dy) {
|
||||
mRecyclerView.scrollBy(0, (int) dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScroll(float dy) {
|
||||
return mRecyclerView.canScrollVertically((int) dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrollContent(float x, float y) {
|
||||
final View v = mRecyclerView;
|
||||
final int[] location = new int[2];
|
||||
v.getLocationInWindow(location);
|
||||
return x >= location[0] && x <= location[0] + v.getWidth()
|
||||
&& y >= location[1] && y <= location[1] + v.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelTouch() {
|
||||
mRecyclerView.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
|
||||
SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldLayoutHeaderBottom() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void topChanged(int offset) {
|
||||
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ import android.annotation.TargetApi;
|
|||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
|
@ -172,7 +173,7 @@ public final class ViewSupport {
|
|||
view.setClipToOutline(clipToOutline);
|
||||
}
|
||||
|
||||
public static void setOutlineProvider(View view, ViewOutlineProviderCompat outlineProvider) {
|
||||
public static void setOutlineProvider(View view, @Nullable ViewOutlineProviderCompat outlineProvider) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
|
||||
view.setOutlineProvider(new ViewOutlineProviderCompat.ViewOutlineProviderL(outlineProvider));
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import android.annotation.TargetApi;
|
|||
import android.graphics.Outline;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
|
||||
|
@ -83,15 +84,18 @@ public abstract class ViewOutlineProviderCompat {
|
|||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public static class ViewOutlineProviderL extends ViewOutlineProvider {
|
||||
|
||||
@Nullable
|
||||
private final ViewOutlineProviderCompat providerCompat;
|
||||
|
||||
public ViewOutlineProviderL(ViewOutlineProviderCompat providerCompat) {
|
||||
public ViewOutlineProviderL(@Nullable ViewOutlineProviderCompat providerCompat) {
|
||||
this.providerCompat = providerCompat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
providerCompat.getOutline(view, new OutlineCompat.OutlineL(outline));
|
||||
if (providerCompat != null) {
|
||||
providerCompat.getOutline(view, new OutlineCompat.OutlineL(outline));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,572 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.view;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.widget.ViewDragHelper;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.GestureDetector.SimpleOnGestureListener;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.OverScroller;
|
||||
|
||||
import org.mariotaku.twidere.R;
|
||||
|
||||
import kotlin.ranges.RangesKt;
|
||||
|
||||
/**
|
||||
* Custom ViewGroup for user profile page like Google+ but with tab swipe
|
||||
*
|
||||
* @author mariotaku
|
||||
*/
|
||||
public class HeaderDrawerLayout extends ViewGroup {
|
||||
|
||||
private final ViewDragHelper mDragHelper;
|
||||
private final OverScroller mScroller;
|
||||
private final GestureDetector mGestureDetector;
|
||||
|
||||
private final InternalContainer mContainer;
|
||||
private final DragCallback mDragCallback;
|
||||
|
||||
private DrawerCallback mDrawerCallback;
|
||||
private boolean mUsingDragHelper;
|
||||
private boolean mScrollingHeaderByGesture, mScrollingContentCallback;
|
||||
private boolean mTouchDown, mTouchingScrollableContent;
|
||||
|
||||
private int mHeaderOffset;
|
||||
private int mTop;
|
||||
|
||||
public HeaderDrawerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HeaderDrawerLayout);
|
||||
final int headerLayoutId = a.getResourceId(R.styleable.HeaderDrawerLayout_hdl_headerLayout, 0);
|
||||
final int contentLayoutId = a.getResourceId(R.styleable.HeaderDrawerLayout_hdl_contentLayout, 0);
|
||||
addView(mContainer = new InternalContainer(this, context, headerLayoutId, contentLayoutId),
|
||||
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
|
||||
a.recycle();
|
||||
mDragHelper = ViewDragHelper.create(this, mDragCallback = new DragCallback(this));
|
||||
mGestureDetector = new GestureDetector(context, new GestureListener(this));
|
||||
mScroller = new OverScroller(context);
|
||||
}
|
||||
|
||||
public HeaderDrawerLayout(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public HeaderDrawerLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mScroller.abortAnimation();
|
||||
mTouchDown = true;
|
||||
mTouchingScrollableContent = isScrollContentCallback(ev.getX(), ev.getY());
|
||||
mUsingDragHelper = false;
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP: {
|
||||
mTouchDown = false;
|
||||
mTouchingScrollableContent = false;
|
||||
mUsingDragHelper = false;
|
||||
}
|
||||
}
|
||||
mGestureDetector.onTouchEvent(ev);
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
if (mDragHelper.shouldInterceptTouchEvent(ev) || mScrollingHeaderByGesture) {
|
||||
mUsingDragHelper = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||
mDragHelper.processTouchEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollVertically(final int direction) {
|
||||
if (direction > 0) {
|
||||
return getHeaderTop() > getHeaderTopMaximum();
|
||||
} else if (direction < 0) {
|
||||
return getHeaderTop() < getHeaderTopMaximum();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final View child = getChildAt(0);
|
||||
|
||||
final int childWidthMeasureSpec = makeChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight());
|
||||
final int childHeightMeasureSpec = makeChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom());
|
||||
|
||||
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
|
||||
super.onMeasure(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);
|
||||
final int left = getPaddingLeft(), right = left + child.getMeasuredWidth();
|
||||
final int top, bottom;
|
||||
if (i == 0) {
|
||||
if (shouldLayoutHeaderBottomCallback() && child.getHeight() != 0) {
|
||||
// How far did we moved?
|
||||
int delta = mHeaderOffset + getPaddingTop() - child.getTop();
|
||||
bottom = child.getBottom() + delta;
|
||||
top = bottom - child.getHeight();
|
||||
} else {
|
||||
top = mHeaderOffset + getPaddingTop();
|
||||
bottom = top + child.getMeasuredHeight();
|
||||
}
|
||||
} else {
|
||||
top = getChildAt(i - 1).getBottom();
|
||||
bottom = top + child.getMeasuredHeight();
|
||||
}
|
||||
child.layout(left, top, right, bottom);
|
||||
notifyOffsetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeScroll() {
|
||||
boolean invalidate = mDragHelper.continueSettling(true);
|
||||
if (!mTouchDown && mScroller.computeScrollOffset()) {
|
||||
if (!invalidate) {
|
||||
offsetHeaderBy(mScroller.getCurrY() - getHeaderTop());
|
||||
}
|
||||
invalidate = true;
|
||||
}
|
||||
updateViewOffset();
|
||||
if (invalidate) {
|
||||
ViewCompat.postInvalidateOnAnimation(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
if (getChildCount() != 1) {
|
||||
throw new IllegalArgumentException("Add subview by XML is not allowed.");
|
||||
}
|
||||
}
|
||||
|
||||
public void flingHeader(float velocity) {
|
||||
if (mTouchDown) {
|
||||
mScroller.abortAnimation();
|
||||
return;
|
||||
}
|
||||
mScroller.fling(0, getHeaderTop(), 0, (int) velocity, 0, 0,
|
||||
mContainer.getHeaderTopMinimum(), mContainer.getHeaderTopMaximum());
|
||||
ViewCompat.postInvalidateOnAnimation(this);
|
||||
}
|
||||
|
||||
public View getContent() {
|
||||
return mContainer.getContent();
|
||||
}
|
||||
|
||||
public View getHeader() {
|
||||
return mContainer.getHeader();
|
||||
}
|
||||
|
||||
public int getHeaderTop() {
|
||||
return mContainer.getTop();
|
||||
}
|
||||
|
||||
public int getHeaderTopMaximum() {
|
||||
return mContainer.getHeaderTopMaximum();
|
||||
}
|
||||
|
||||
public int getHeaderTopMinimum() {
|
||||
return mContainer.getHeaderTopMinimum();
|
||||
}
|
||||
|
||||
public void setDrawerCallback(DrawerCallback callback) {
|
||||
mDrawerCallback = callback;
|
||||
}
|
||||
|
||||
private boolean canScrollCallback(float dy) {
|
||||
return mDrawerCallback.canScroll(dy);
|
||||
}
|
||||
|
||||
private void cancelTouchCallback() {
|
||||
mDrawerCallback.cancelTouch();
|
||||
}
|
||||
|
||||
private void flingCallback(float velocity) {
|
||||
mDrawerCallback.fling(velocity);
|
||||
}
|
||||
|
||||
private float getDragTouchSlop() {
|
||||
return mDragHelper.getTouchSlop();
|
||||
}
|
||||
|
||||
private int getScrollRange() {
|
||||
return mContainer.getScrollRange();
|
||||
}
|
||||
|
||||
private boolean isScrollContentCallback(float x, float y) {
|
||||
return mDrawerCallback.isScrollContent(x, y);
|
||||
}
|
||||
|
||||
private boolean isScrollingContentCallback() {
|
||||
return mScrollingContentCallback;
|
||||
}
|
||||
|
||||
private void setScrollingContentCallback(boolean scrolling) {
|
||||
mScrollingContentCallback = scrolling;
|
||||
}
|
||||
|
||||
private boolean isScrollingHeaderByHelper() {
|
||||
return mDragCallback.isScrollingHeaderByHelper();
|
||||
}
|
||||
|
||||
private boolean isTouchingScrollableContent() {
|
||||
return mTouchingScrollableContent;
|
||||
}
|
||||
|
||||
private boolean isUsingDragHelper() {
|
||||
return mUsingDragHelper;
|
||||
}
|
||||
|
||||
private boolean isValidScroll(float direction, float other) {
|
||||
return Math.abs(direction) > getDragTouchSlop() && Math.abs(direction) > Math.abs(other);
|
||||
}
|
||||
|
||||
private static int makeChildMeasureSpec(int spec, int padding) {
|
||||
final int size = MeasureSpec.getSize(spec), mode = MeasureSpec.getMode(spec);
|
||||
return MeasureSpec.makeMeasureSpec(size - padding, mode);
|
||||
}
|
||||
|
||||
private void notifyOffsetChanged() {
|
||||
final int top = getHeaderTop();
|
||||
if (mTop == top) return;
|
||||
mHeaderOffset = top - getPaddingTop();
|
||||
mDrawerCallback.topChanged(top);
|
||||
mTop = top;
|
||||
}
|
||||
|
||||
private void offsetHeaderBy(int dy) {
|
||||
final int prevTop = mContainer.getTop();
|
||||
final int clampedDy = RangesKt.coerceIn(prevTop + dy, getHeaderTopMinimum(), getHeaderTopMaximum()) - prevTop;
|
||||
mContainer.offsetTopAndBottom(clampedDy);
|
||||
}
|
||||
|
||||
private void scrollByCallback(float dy) {
|
||||
final int top = getHeaderTop();
|
||||
setScrollingContentCallback(top > getHeaderTopMinimum() && top < getHeaderTopMaximum());
|
||||
mDrawerCallback.scrollBy(dy);
|
||||
}
|
||||
|
||||
private void setScrollingHeaderByGesture(boolean scrolling) {
|
||||
mScrollingHeaderByGesture = scrolling;
|
||||
}
|
||||
|
||||
private boolean shouldLayoutHeaderBottomCallback() {
|
||||
if (mDragCallback == null || isInEditMode()) return false;
|
||||
return mDrawerCallback.shouldLayoutHeaderBottom();
|
||||
}
|
||||
|
||||
private void updateViewOffset() {
|
||||
}
|
||||
|
||||
public interface DrawerCallback {
|
||||
|
||||
boolean canScroll(float dy);
|
||||
|
||||
void cancelTouch();
|
||||
|
||||
void fling(float velocity);
|
||||
|
||||
boolean isScrollContent(float x, float y);
|
||||
|
||||
void scrollBy(float dy);
|
||||
|
||||
boolean shouldLayoutHeaderBottom();
|
||||
|
||||
void topChanged(int offset);
|
||||
}
|
||||
|
||||
private static class DragCallback extends ViewDragHelper.Callback {
|
||||
|
||||
private final HeaderDrawerLayout mDrawer;
|
||||
private long mTime;
|
||||
private float mDx, mDy, mVelocity;
|
||||
private boolean mScrollingHeaderByHelper;
|
||||
|
||||
public DragCallback(HeaderDrawerLayout drawer) {
|
||||
mDrawer = drawer;
|
||||
mTime = -1;
|
||||
mDx = Float.NaN;
|
||||
mDy = Float.NaN;
|
||||
mVelocity = Float.NaN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDragStateChanged(int state) {
|
||||
switch (state) {
|
||||
case ViewDragHelper.STATE_SETTLING:
|
||||
case ViewDragHelper.STATE_DRAGGING: {
|
||||
mScrollingHeaderByHelper = false;
|
||||
break;
|
||||
}
|
||||
case ViewDragHelper.STATE_IDLE: {
|
||||
if (mTime > 0 && !Float.isNaN(mVelocity)) {
|
||||
final float velocity = mVelocity;
|
||||
if (velocity < 0 && mDrawer.getHeaderTop() <= mDrawer.getHeaderTopMinimum()) {
|
||||
mDrawer.flingCallback(-velocity);
|
||||
}
|
||||
}
|
||||
mTime = -1;
|
||||
mDx = Float.NaN;
|
||||
mDy = Float.NaN;
|
||||
mVelocity = Float.NaN;
|
||||
mScrollingHeaderByHelper = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
super.onViewDragStateChanged(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
|
||||
super.onViewPositionChanged(changedView, left, top, dx, dy);
|
||||
final long time = SystemClock.uptimeMillis();
|
||||
final float timeDelta = time - mTime;
|
||||
mVelocity = mDy / timeDelta * 1000;
|
||||
mTime = time;
|
||||
mDy = dy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewReleased(View releasedChild, float xVel, float yVel) {
|
||||
mDrawer.mDragHelper.flingCapturedView(mDrawer.getPaddingLeft(),
|
||||
mDrawer.getHeaderTopMinimum(), mDrawer.getPaddingLeft(),
|
||||
mDrawer.getHeaderTopMaximum());
|
||||
ViewCompat.postInvalidateOnAnimation(mDrawer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewVerticalDragRange(View child) {
|
||||
return mDrawer.getScrollRange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryCaptureView(View view, int pointerId) {
|
||||
return view == mDrawer.mContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionHorizontal(View child, int left, int dx) {
|
||||
mDx = dx;
|
||||
return mDrawer.getPaddingLeft();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionVertical(final View child, final int top, final int dy) {
|
||||
final int current = mDrawer.getHeaderTop();
|
||||
if (!Float.isNaN(mDx) && mDrawer.isValidScroll(mDx, dy)) {
|
||||
mScrollingHeaderByHelper = false;
|
||||
return current;
|
||||
}
|
||||
if (dy > 0 && mDrawer.canScrollCallback(-dy) && mDrawer.isTouchingScrollableContent()) {
|
||||
if (!mDrawer.isUsingDragHelper()) {
|
||||
// Scrolling up while list still has space to scroll, so make header still
|
||||
mScrollingHeaderByHelper = false;
|
||||
return current;
|
||||
} else {
|
||||
mDrawer.scrollByCallback(-dy);
|
||||
mScrollingHeaderByHelper = false;
|
||||
return current;
|
||||
}
|
||||
}
|
||||
final int min = mDrawer.getHeaderTopMinimum(), max = mDrawer.getHeaderTopMaximum();
|
||||
if (top < min && mDrawer.isTouchingScrollableContent() && mDrawer.isUsingDragHelper()) {
|
||||
mDrawer.scrollByCallback(-dy);
|
||||
}
|
||||
mScrollingHeaderByHelper = true;
|
||||
return RangesKt.coerceIn(top, min, max);
|
||||
}
|
||||
|
||||
private boolean isScrollingHeaderByHelper() {
|
||||
return mScrollingHeaderByHelper;
|
||||
}
|
||||
}
|
||||
|
||||
private static class GestureListener extends SimpleOnGestureListener {
|
||||
|
||||
private final HeaderDrawerLayout mDrawer;
|
||||
|
||||
public GestureListener(HeaderDrawerLayout drawer) {
|
||||
mDrawer = drawer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
if (!mDrawer.isUsingDragHelper() && mDrawer.isValidScroll(distanceY, distanceX)) {
|
||||
final int offset = mDrawer.getHeaderTop(), min = mDrawer.getHeaderTopMinimum();
|
||||
if (!mDrawer.canScrollCallback(-1)) {
|
||||
if (distanceY < 0) {
|
||||
if (!mDrawer.isScrollingHeaderByHelper()) {
|
||||
mDrawer.offsetHeaderBy(Math.round(-distanceY));
|
||||
}
|
||||
mDrawer.setScrollingHeaderByGesture(true);
|
||||
} else if (distanceY > 0 && offset > min) {
|
||||
// Scrolling up when scrolling to list top, so we cancel touch event and scrolling header up
|
||||
mDrawer.cancelTouchCallback();
|
||||
if (!mDrawer.isScrollingHeaderByHelper()) {
|
||||
mDrawer.offsetHeaderBy(Math.round(-distanceY));
|
||||
}
|
||||
} else if (offset <= min) {
|
||||
mDrawer.scrollByCallback(-distanceX);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
final int top = mDrawer.getHeaderTop(), min = mDrawer.getHeaderTopMinimum();
|
||||
final boolean showingFullContent = top <= min, flingUp = velocityY < 0;
|
||||
final boolean verticalFling = Math.abs(velocityY) > Math.abs(velocityX);
|
||||
if (!verticalFling) return true;
|
||||
if (showingFullContent) {
|
||||
if (flingUp) {
|
||||
// Fling list up when showing full content
|
||||
if (mDrawer.isScrollingContentCallback()) {
|
||||
mDrawer.flingCallback(-velocityY);
|
||||
}
|
||||
} else {
|
||||
// Fling down when list reached top and not dragging user ViewDragHelper,
|
||||
// so we fling header down here
|
||||
if (!mDrawer.canScrollCallback(1) && !mDrawer.isUsingDragHelper()) {
|
||||
mDrawer.flingHeader(velocityY);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Header still visible
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
mDrawer.setScrollingHeaderByGesture(false);
|
||||
mDrawer.setScrollingContentCallback(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
private static class InternalContainer extends ViewGroup {
|
||||
|
||||
private final HeaderDrawerLayout mParent;
|
||||
private final View mHeaderView, mContentView;
|
||||
|
||||
public InternalContainer(HeaderDrawerLayout parent, Context context, int headerLayoutId, int contentLayoutId) {
|
||||
super(context);
|
||||
mParent = parent;
|
||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||
addView(mHeaderView = inflater.inflate(headerLayoutId, this, false));
|
||||
addView(mContentView = inflater.inflate(contentLayoutId, this, false));
|
||||
}
|
||||
|
||||
public View getContent() {
|
||||
return mContentView;
|
||||
}
|
||||
|
||||
public View getHeader() {
|
||||
return mHeaderView;
|
||||
}
|
||||
|
||||
public int getHeaderTopMaximum() {
|
||||
return mParent.getPaddingTop();
|
||||
}
|
||||
|
||||
public int getHeaderTopMinimum() {
|
||||
return mParent.getPaddingTop() - mHeaderView.getHeight();
|
||||
}
|
||||
|
||||
public int getScrollRange() {
|
||||
return getHeaderTopMaximum() - getHeaderTopMinimum();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
|
||||
int heightSum = 0;
|
||||
for (int i = 0, j = getChildCount(); i < j; i++) {
|
||||
final View child = getChildAt(i);
|
||||
final LayoutParams lp = child.getLayoutParams();
|
||||
final int childHeightSpec;
|
||||
if (lp.height == LayoutParams.MATCH_PARENT) {
|
||||
childHeightSpec = heightMeasureSpec;
|
||||
} else if (lp.height == LayoutParams.WRAP_CONTENT) {
|
||||
childHeightSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.UNSPECIFIED);
|
||||
} else {
|
||||
childHeightSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.EXACTLY);
|
||||
}
|
||||
child.measure(widthMeasureSpec, childHeightSpec);
|
||||
heightSum += child.getMeasuredHeight();
|
||||
}
|
||||
final int hSpec = MeasureSpec.makeMeasureSpec(heightSum, MeasureSpec.EXACTLY);
|
||||
super.onMeasure(widthMeasureSpec, hSpec);
|
||||
}
|
||||
|
||||
@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);
|
||||
final int left = getPaddingLeft(), right = left + child.getMeasuredWidth();
|
||||
final int top = i == 0 ? getPaddingTop() : getChildAt(i - 1).getBottom();
|
||||
final int bottom = top + child.getMeasuredHeight();
|
||||
child.layout(left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetTopAndBottom(int offset) {
|
||||
super.offsetTopAndBottom(offset);
|
||||
mParent.notifyOffsetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -22,9 +22,13 @@ package android.support.design.widget
|
|||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.OverScroller
|
||||
|
||||
internal open class AccessorHeaderBehavior<V : View>(context: Context, attrs: AttributeSet? = null) : HeaderBehavior<V>(context, attrs) {
|
||||
|
||||
internal val scroller: OverScroller?
|
||||
get() = mScroller
|
||||
|
||||
internal override fun getScrollRangeForDragFling(view: V): Int {
|
||||
return super.getScrollRangeForDragFling(view)
|
||||
}
|
||||
|
@ -45,11 +49,21 @@ internal open class AccessorHeaderBehavior<V : View>(context: Context, attrs: At
|
|||
return super.getTopBottomOffsetForScrollingSibling()
|
||||
}
|
||||
|
||||
internal override fun onFlingFinished(parent: CoordinatorLayout?, layout: V) {
|
||||
internal override fun onFlingFinished(parent: CoordinatorLayout, layout: V) {
|
||||
super.onFlingFinished(parent, layout)
|
||||
}
|
||||
|
||||
internal override fun canDragView(view: V): Boolean {
|
||||
return super.canDragView(view)
|
||||
}
|
||||
|
||||
internal fun scrollAccessor(coordinatorLayout: CoordinatorLayout, header: V,
|
||||
dy: Int, minOffset: Int, maxOffset: Int): Int {
|
||||
return scroll(coordinatorLayout, header, dy, minOffset, maxOffset)
|
||||
}
|
||||
|
||||
internal fun flingAccessor(coordinatorLayout: CoordinatorLayout, layout: V, minOffset: Int,
|
||||
maxOffset: Int, velocityY: Float): Boolean {
|
||||
return fling(coordinatorLayout, layout, minOffset, maxOffset, velocityY)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package android.support.design.widget
|
||||
|
||||
import android.animation.TimeInterpolator
|
||||
|
||||
object AnimationUtilsAccessor {
|
||||
|
||||
val DECELERATE_INTERPOLATOR: TimeInterpolator = AnimationUtils.DECELERATE_INTERPOLATOR
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package android.support.v7.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
|
||||
open class FixedLinearLayoutManager : LinearLayoutManager {
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)
|
||||
|
||||
internal override fun findOneVisibleChild(fromIndex: Int, toIndex: Int, completelyVisible: Boolean, acceptPartiallyVisible: Boolean): View? {
|
||||
// XXX Fixed NPE by add a simple check to child view count
|
||||
return if (childCount < 0) null else super.findOneVisibleChild(fromIndex, toIndex, completelyVisible, acceptPartiallyVisible)
|
||||
}
|
||||
|
||||
}
|
|
@ -19,4 +19,4 @@ fun LongArray.toStringArray(): Array<String> {
|
|||
return Array(this.size) { idx -> this[idx].toString() }
|
||||
}
|
||||
|
||||
fun <T> T.weak(): WeakReference<T> = WeakReference(this)
|
||||
fun <T> T.toWeak(): WeakReference<T> = WeakReference(this)
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.ktextension
|
||||
|
||||
import java.lang.ref.WeakReference
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
class WeakDelegate<T> {
|
||||
|
||||
private var weakRef: WeakReference<T>? = null
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
|
||||
return weakRef?.get()
|
||||
}
|
||||
|
||||
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
|
||||
weakRef = if (value != null) WeakReference(value) else null
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> weak(): WeakDelegate<T> = WeakDelegate()
|
|
@ -12,7 +12,7 @@ import nl.komponents.kovenant.combine.and
|
|||
import nl.komponents.kovenant.task
|
||||
import nl.komponents.kovenant.ui.failUi
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import org.mariotaku.ktextension.weak
|
||||
import org.mariotaku.ktextension.toWeak
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.activity.iface.IBaseActivity
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_INTENT
|
||||
|
@ -55,7 +55,7 @@ class InvalidAccountAlertActivity : FragmentActivity(), IBaseActivity<InvalidAcc
|
|||
|
||||
fun removeInvalidAccounts() {
|
||||
val am = AccountManager.get(this)
|
||||
val weakThis = weak()
|
||||
val weakThis = toWeak()
|
||||
val invalidAccounts = AccountUtils.getAccounts(am).filter { !am.isAccountValid(it) }
|
||||
(showProgressDialog("remove_invalid_accounts") and task {
|
||||
invalidAccounts.forEach { account ->
|
||||
|
|
|
@ -536,7 +536,7 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos
|
|||
|
||||
private fun saveMediaToContentUri(data: Uri) {
|
||||
val fileInfo = getCurrentCacheFileInfo(viewPager.currentItem) ?: return
|
||||
val weakThis = weak()
|
||||
val weakThis = toWeak()
|
||||
(showProgressDialog("save_media_to_progress") and task {
|
||||
val a = weakThis.get() ?: throw InterruptedException()
|
||||
fileInfo.inputStream().use { st ->
|
||||
|
|
|
@ -35,7 +35,6 @@ import nl.komponents.kovenant.android.startKovenant
|
|||
import nl.komponents.kovenant.android.stopKovenant
|
||||
import nl.komponents.kovenant.task
|
||||
import okhttp3.Dns
|
||||
import org.mariotaku.commons.logansquare.LoganSquareMapperFinder
|
||||
import org.mariotaku.kpreferences.KPreferences
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.kpreferences.set
|
||||
|
@ -56,7 +55,6 @@ import org.mariotaku.twidere.model.DefaultFeatures
|
|||
import org.mariotaku.twidere.receiver.ConnectivityStateReceiver
|
||||
import org.mariotaku.twidere.service.StreamingService
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.concurrent.ConstantFuture
|
||||
import org.mariotaku.twidere.util.content.TwidereSQLiteOpenHelper
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponent
|
||||
import org.mariotaku.twidere.util.emoji.EmojiOneShortCodeMap
|
||||
|
@ -71,8 +69,6 @@ import org.mariotaku.twidere.util.refresh.AutoRefreshController
|
|||
import org.mariotaku.twidere.util.sync.DataSyncProvider
|
||||
import org.mariotaku.twidere.util.sync.SyncController
|
||||
import java.util.*
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -309,16 +305,10 @@ class TwidereApplication : Application(), OnSharedPreferenceChangeListener {
|
|||
Class.forName(AsyncTask::class.java.name)
|
||||
} catch (ignore: ClassNotFoundException) {
|
||||
}
|
||||
LoganSquareMapperFinder.setDefaultExecutor(object : LoganSquareMapperFinder.FutureExecutor {
|
||||
override fun <T> submit(callable: Callable<T>): Future<T> {
|
||||
return ConstantFuture(callable.call())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val KEY_KEYBOARD_SHORTCUT_INITIALIZED = "keyboard_shortcut_initialized"
|
||||
var instance: TwidereApplication? = null
|
||||
private set
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ import android.database.ContentObserver
|
|||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import org.mariotaku.ktextension.weak
|
||||
import org.mariotaku.ktextension.toWeak
|
||||
import org.mariotaku.twidere.extension.queryAll
|
||||
import org.mariotaku.twidere.extension.queryCount
|
||||
|
||||
|
@ -40,7 +40,7 @@ class CursorObjectTiledDataSource<T>(
|
|||
) : TiledDataSource<T>() {
|
||||
|
||||
init {
|
||||
val weakThis = weak()
|
||||
val weakThis = toWeak()
|
||||
val observer = object : ContentObserver(MainHandler) {
|
||||
override fun onChange(selfChange: Boolean) {
|
||||
weakThis.get()?.invalidate()
|
||||
|
|
|
@ -36,9 +36,11 @@ import org.mariotaku.twidere.adapter.LoadMoreSupportAdapter
|
|||
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
|
||||
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition
|
||||
import org.mariotaku.twidere.fragment.iface.RefreshScrollTopInterface
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.ContentScrollHandler
|
||||
import org.mariotaku.twidere.util.RecyclerViewScrollHandler
|
||||
import org.mariotaku.twidere.util.ThemeUtils
|
||||
import org.mariotaku.twidere.util.TwidereColorUtils
|
||||
import org.mariotaku.twidere.view.ExtendedSwipeRefreshLayout
|
||||
import org.mariotaku.twidere.view.HeaderDrawerLayout
|
||||
import org.mariotaku.twidere.view.iface.IExtendedView
|
||||
|
||||
/**
|
||||
|
@ -46,7 +48,7 @@ import org.mariotaku.twidere.view.iface.IExtendedView
|
|||
*/
|
||||
abstract class AbsContentRecyclerViewFragment<A : LoadMoreSupportAdapter<RecyclerView.ViewHolder>,
|
||||
L : RecyclerView.LayoutManager> : BaseFragment(), SwipeRefreshLayout.OnRefreshListener,
|
||||
HeaderDrawerLayout.DrawerCallback, RefreshScrollTopInterface, IControlBarActivity.ControlBarOffsetListener,
|
||||
RefreshScrollTopInterface, IControlBarActivity.ControlBarOffsetListener,
|
||||
ContentScrollHandler.ContentListSupport<A>, ControlBarShowHideHelper.ControlBarAnimationListener {
|
||||
|
||||
lateinit var layoutManager: L
|
||||
|
@ -57,7 +59,6 @@ abstract class AbsContentRecyclerViewFragment<A : LoadMoreSupportAdapter<Recycle
|
|||
private set
|
||||
|
||||
// Callbacks and listeners
|
||||
private lateinit var drawerCallback: SimpleDrawerCallback
|
||||
lateinit var scrollListener: RecyclerViewScrollHandler<A>
|
||||
// Data fields
|
||||
private val systemWindowsInsets = Rect()
|
||||
|
@ -84,22 +85,6 @@ abstract class AbsContentRecyclerViewFragment<A : LoadMoreSupportAdapter<Recycle
|
|||
swipeLayout.isRefreshing = layoutRefreshing
|
||||
}
|
||||
|
||||
override fun canScroll(dy: Float): Boolean {
|
||||
return drawerCallback.canScroll(dy)
|
||||
}
|
||||
|
||||
override fun cancelTouch() {
|
||||
drawerCallback.cancelTouch()
|
||||
}
|
||||
|
||||
override fun fling(velocity: Float) {
|
||||
drawerCallback.fling(velocity)
|
||||
}
|
||||
|
||||
override fun isScrollContent(x: Float, y: Float): Boolean {
|
||||
return drawerCallback.isScrollContent(x, y)
|
||||
}
|
||||
|
||||
override fun onControlBarOffsetChanged(activity: IControlBarActivity, offset: Float) {
|
||||
updateRefreshProgressOffset()
|
||||
}
|
||||
|
@ -115,10 +100,6 @@ abstract class AbsContentRecyclerViewFragment<A : LoadMoreSupportAdapter<Recycle
|
|||
updateRefreshProgressOffset()
|
||||
}
|
||||
|
||||
override fun scrollBy(dy: Float) {
|
||||
drawerCallback.scrollBy(dy)
|
||||
}
|
||||
|
||||
override fun scrollToStart(): Boolean {
|
||||
scrollToPositionWithOffset(0, 0)
|
||||
recyclerView.stopScroll()
|
||||
|
@ -148,14 +129,6 @@ abstract class AbsContentRecyclerViewFragment<A : LoadMoreSupportAdapter<Recycle
|
|||
}
|
||||
}
|
||||
|
||||
override fun shouldLayoutHeaderBottom(): Boolean {
|
||||
return drawerCallback.shouldLayoutHeaderBottom()
|
||||
}
|
||||
|
||||
override fun topChanged(offset: Int) {
|
||||
drawerCallback.topChanged(offset)
|
||||
}
|
||||
|
||||
var refreshEnabled: Boolean
|
||||
get() = swipeLayout.isEnabled
|
||||
set(value) {
|
||||
|
@ -180,7 +153,6 @@ abstract class AbsContentRecyclerViewFragment<A : LoadMoreSupportAdapter<Recycle
|
|||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
drawerCallback = SimpleDrawerCallback(recyclerView)
|
||||
|
||||
val backgroundColor = ThemeUtils.getColorBackground(context)
|
||||
val colorRes = TwidereColorUtils.getContrastYIQ(backgroundColor,
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
package org.mariotaku.twidere.fragment
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.animation.ArgbEvaluator
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
|
@ -30,7 +29,6 @@ import android.content.Intent
|
|||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.net.Uri
|
||||
import android.nfc.NdefMessage
|
||||
import android.nfc.NdefRecord
|
||||
|
@ -79,7 +77,6 @@ import nl.komponents.kovenant.ui.alwaysUi
|
|||
import nl.komponents.kovenant.ui.failUi
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import org.mariotaku.chameleon.Chameleon
|
||||
import org.mariotaku.chameleon.ChameleonUtils
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.ktextension.*
|
||||
import org.mariotaku.library.objectcursor.ObjectCursor
|
||||
|
@ -136,7 +133,9 @@ import org.mariotaku.twidere.util.menu.TwidereMenuInfo
|
|||
import org.mariotaku.twidere.util.shortcut.ShortcutCreator
|
||||
import org.mariotaku.twidere.util.support.ActivitySupport
|
||||
import org.mariotaku.twidere.util.support.ActivitySupport.TaskDescriptionCompat
|
||||
import org.mariotaku.twidere.util.support.ViewSupport
|
||||
import org.mariotaku.twidere.util.support.WindowSupport
|
||||
import org.mariotaku.twidere.util.support.view.ViewOutlineProviderCompat
|
||||
import org.mariotaku.twidere.view.TabPagerIndicator
|
||||
import org.mariotaku.twidere.view.iface.IExtendedView.OnSizeChangedListener
|
||||
import java.lang.ref.WeakReference
|
||||
|
@ -152,7 +151,6 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
|||
get() = coordinatorLayout.toolbar
|
||||
|
||||
private lateinit var profileBirthdayBanner: View
|
||||
private lateinit var actionBarBackground: ActionBarDrawable
|
||||
private lateinit var pagerAdapter: SupportTabsAdapter
|
||||
|
||||
// Data fields
|
||||
|
@ -169,7 +167,6 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
|||
private var actionBarShadowColor: Int = 0
|
||||
private var uiColor: Int = 0
|
||||
private var primaryColor: Int = 0
|
||||
private var primaryColorDark: Int = 0
|
||||
private var nameFirst: Boolean = false
|
||||
private var previousTabItemIsDark: Int = 0
|
||||
private var previousActionBarItemIsDark: Int = 0
|
||||
|
@ -635,10 +632,8 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
|||
profileHeaderBackground.setBackgroundColor(cardBackgroundColor)
|
||||
toolbarTabs.setBackgroundColor(cardBackgroundColor)
|
||||
|
||||
actionBarBackground = ActionBarDrawable(ResourcesCompat.getDrawable(activity.resources,
|
||||
R.drawable.shadow_user_banner_action_bar, null)!!)
|
||||
setupBaseActionBar()
|
||||
setupViewStyle()
|
||||
setupViewSettings()
|
||||
setupUserPages()
|
||||
|
||||
getUserInfo(accountKey, userKey, screenName, false)
|
||||
|
@ -1269,20 +1264,18 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
|||
uiColor = if (color != 0) color else theme.colorPrimary
|
||||
previousActionBarItemIsDark = 0
|
||||
previousTabItemIsDark = 0
|
||||
setupBaseActionBar()
|
||||
setupViewStyle()
|
||||
val activity = activity as BaseActivity
|
||||
if (theme.isToolbarColored) {
|
||||
primaryColor = color
|
||||
primaryColor = if (theme.isToolbarColored) {
|
||||
color
|
||||
} else {
|
||||
primaryColor = theme.colorToolbar
|
||||
theme.colorToolbar
|
||||
}
|
||||
primaryColorDark = ChameleonUtils.darkenColor(primaryColor)
|
||||
actionBarBackground.color = primaryColor
|
||||
val taskColor: Int
|
||||
if (theme.isToolbarColored) {
|
||||
taskColor = ColorUtils.setAlphaComponent(color, 0xFF)
|
||||
(toolbar.background as? ActionBarDrawable)?.color = primaryColor
|
||||
val taskColor = if (theme.isToolbarColored) {
|
||||
ColorUtils.setAlphaComponent(color, 0xFF)
|
||||
} else {
|
||||
taskColor = ColorUtils.setAlphaComponent(theme.colorToolbar, 0xFF)
|
||||
ColorUtils.setAlphaComponent(theme.colorToolbar, 0xFF)
|
||||
}
|
||||
val user = this.user
|
||||
if (user != null) {
|
||||
|
@ -1298,17 +1291,28 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
|||
profileBanner.setBackgroundColor(color)
|
||||
}
|
||||
|
||||
private fun setupBaseActionBar() {
|
||||
private fun setupViewStyle() {
|
||||
val activity = activity as? LinkHandlerActivity ?: return
|
||||
val actionBar = activity.supportActionBar ?: return
|
||||
if (!ThemeUtils.isWindowFloating(activity) && ThemeUtils.isTransparentBackground(activity.currentThemeBackgroundOption)) {
|
||||
profileBanner.alpha = activity.currentThemeBackgroundAlpha / 255f
|
||||
}
|
||||
actionBar.setBackgroundDrawable(actionBarBackground)
|
||||
|
||||
actionBar.setBackgroundDrawable(ActionBarDrawable(ResourcesCompat.getDrawable(activity.resources,
|
||||
R.drawable.shadow_user_banner_action_bar, null)!!))
|
||||
|
||||
val actionBarElevation = ThemeUtils.getSupportActionBarElevation(activity)
|
||||
ViewCompat.setElevation(toolbar, actionBarElevation)
|
||||
// ViewCompat.setElevation(profileHeader, actionBarElevation)
|
||||
ViewCompat.setElevation(statusBarBackground, actionBarElevation * 2)
|
||||
|
||||
ViewSupport.setOutlineProvider(toolbar, ViewOutlineProviderCompat.BACKGROUND)
|
||||
// ViewSupport.setOutlineProvider(profileHeader, ViewOutlineProviderCompat.BOUNDS)
|
||||
ViewSupport.setOutlineProvider(statusBarBackground, null)
|
||||
}
|
||||
|
||||
|
||||
private fun setupViewStyle() {
|
||||
private fun setupViewSettings() {
|
||||
profileImage.style = preferences[profileImageStyleKey]
|
||||
|
||||
val lightFont = preferences[lightFontKey]
|
||||
|
@ -1360,63 +1364,6 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateScrollOffset(offset: Int) {
|
||||
val spaceHeight = profileBannerSpace.height
|
||||
val factor = (if (spaceHeight == 0) 0f else offset / spaceHeight.toFloat()).coerceIn(0f, 1f)
|
||||
|
||||
val activity = activity as BaseActivity
|
||||
|
||||
|
||||
val statusBarColor = sArgbEvaluator.evaluate(factor, 0xA0000000.toInt(),
|
||||
ChameleonUtils.darkenColor(primaryColorDark)) as Int
|
||||
val window = activity.window
|
||||
WindowSupport.setLightStatusBar(window, ThemeUtils.isLightColor(statusBarColor))
|
||||
val stackedTabColor = primaryColor
|
||||
|
||||
|
||||
val profileContentHeight = profileHeaderBackground.height.toFloat()
|
||||
val tabOutlineAlphaFactor: Float
|
||||
if (offset - spaceHeight > 0) {
|
||||
tabOutlineAlphaFactor = 1f - ((offset - spaceHeight) / profileContentHeight).coerceIn(0f, 1f)
|
||||
} else {
|
||||
tabOutlineAlphaFactor = 1f
|
||||
}
|
||||
|
||||
actionBarBackground.apply {
|
||||
this.factor = factor
|
||||
this.outlineAlphaFactor = tabOutlineAlphaFactor
|
||||
}
|
||||
|
||||
val currentTabColor = sArgbEvaluator.evaluate(tabOutlineAlphaFactor,
|
||||
stackedTabColor, cardBackgroundColor) as Int
|
||||
|
||||
val tabBackground = toolbarTabs.background
|
||||
(tabBackground as ColorDrawable).color = currentTabColor
|
||||
val tabItemIsDark = ThemeUtils.isLightColor(currentTabColor)
|
||||
|
||||
if (previousTabItemIsDark == 0 || (if (tabItemIsDark) 1 else -1) != previousTabItemIsDark) {
|
||||
val tabContrastColor = ThemeUtils.getColorDependent(currentTabColor)
|
||||
toolbarTabs.setIconColor(tabContrastColor)
|
||||
toolbarTabs.setLabelColor(tabContrastColor)
|
||||
val theme = Chameleon.getOverrideTheme(activity, activity)
|
||||
if (theme.isToolbarColored) {
|
||||
toolbarTabs.setStripColor(tabContrastColor)
|
||||
} else {
|
||||
toolbarTabs.setStripColor(ThemeUtils.getOptimalAccentColor(uiColor, tabContrastColor))
|
||||
}
|
||||
toolbarTabs.updateAppearance()
|
||||
}
|
||||
previousTabItemIsDark = if (tabItemIsDark) 1 else -1
|
||||
|
||||
val currentActionBarColor = sArgbEvaluator.evaluate(factor, actionBarShadowColor,
|
||||
stackedTabColor) as Int
|
||||
val actionItemIsDark = ThemeUtils.isLightColor(currentActionBarColor)
|
||||
if (previousActionBarItemIsDark == 0 || (if (actionItemIsDark) 1 else -1) != previousActionBarItemIsDark) {
|
||||
ThemeUtils.applyToolbarItemColor(activity, toolbar, currentActionBarColor)
|
||||
}
|
||||
previousActionBarItemIsDark = if (actionItemIsDark) 1 else -1
|
||||
}
|
||||
|
||||
override var controlBarOffset: Float
|
||||
get() = 0f
|
||||
set(value) = Unit //Ignore
|
||||
|
@ -1649,7 +1596,6 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
|||
|
||||
companion object {
|
||||
|
||||
private val sArgbEvaluator = ArgbEvaluator()
|
||||
private val LOADER_ID_USER = 1
|
||||
private val LOADER_ID_FRIENDSHIP = 2
|
||||
|
||||
|
|
|
@ -215,7 +215,7 @@ class FilteredUsersFragment : BaseFiltersFragment() {
|
|||
}
|
||||
|
||||
private fun exportToMutedUsers(accountKey: UserKey, items: Array<UserKey>) {
|
||||
val weakThis = this.weak()
|
||||
val weakThis = this.toWeak()
|
||||
showProgressDialog("export_to_muted").then {
|
||||
val fragment = weakThis.get() ?: throw InterruptedException()
|
||||
val am = AccountManager.get(fragment.context)
|
||||
|
|
|
@ -3,9 +3,6 @@ package org.mariotaku.twidere.fragment.iface
|
|||
import android.support.v4.app.FragmentActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/3/16.
|
||||
*/
|
||||
interface IToolBarSupportFragment {
|
||||
|
||||
val toolbar: Toolbar
|
||||
|
|
|
@ -9,7 +9,7 @@ import android.view.MenuItem
|
|||
import com.squareup.otto.Subscribe
|
||||
import nl.komponents.kovenant.combine.and
|
||||
import nl.komponents.kovenant.ui.alwaysUi
|
||||
import org.mariotaku.ktextension.weak
|
||||
import org.mariotaku.ktextension.toWeak
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.SYNC_PREFERENCES_NAME
|
||||
import org.mariotaku.twidere.constant.dataSyncProviderInfoKey
|
||||
|
@ -80,7 +80,7 @@ class SyncSettingsFragment : BasePreferenceFragment() {
|
|||
|
||||
private fun cleanupAndDisconnect() {
|
||||
val providerInfo = kPreferences[dataSyncProviderInfoKey] ?: return
|
||||
val weakThis = weak()
|
||||
val weakThis = toWeak()
|
||||
val task = showProgressDialog("cleanup_sync_cache").
|
||||
and(syncController.cleanupSyncCache(providerInfo))
|
||||
task.alwaysUi {
|
||||
|
|
|
@ -28,7 +28,7 @@ import android.support.v4.view.MenuItemCompat
|
|||
import android.support.v7.widget.ActionMenuView
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import org.mariotaku.ktextension.weak
|
||||
import org.mariotaku.ktextension.toWeak
|
||||
import org.mariotaku.twidere.extension.view.findItemView
|
||||
import org.mariotaku.twidere.graphic.like.LikeAnimationDrawable
|
||||
import org.mariotaku.twidere.graphic.like.LikeAnimationDrawable.Style
|
||||
|
@ -75,7 +75,7 @@ class FavoriteItemProvider(context: Context) : ActionProvider(context) {
|
|||
}
|
||||
|
||||
private class ViewCallback(view: View) : Drawable.Callback {
|
||||
private val viewRef = view.weak()
|
||||
private val viewRef = view.toWeak()
|
||||
|
||||
override fun invalidateDrawable(who: Drawable) {
|
||||
val view = viewRef.get() ?: return
|
||||
|
|
|
@ -24,14 +24,17 @@ import android.content.Context
|
|||
import android.support.design.widget.AccessorHeaderBehavior
|
||||
import android.support.design.widget.CoordinatorLayout
|
||||
import android.support.graphics.drawable.ArgbEvaluator
|
||||
import android.support.v4.view.ViewCompat
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.fragment_user.view.*
|
||||
import kotlinx.android.synthetic.main.header_user.view.*
|
||||
import org.mariotaku.chameleon.Chameleon
|
||||
import org.mariotaku.chameleon.ChameleonUtils
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.ktextension.weak
|
||||
import org.mariotaku.twidere.constant.themeBackgroundAlphaKey
|
||||
import org.mariotaku.twidere.constant.themeBackgroundOptionKey
|
||||
import org.mariotaku.twidere.extension.view.measureChildIgnoringInsets
|
||||
|
@ -39,7 +42,8 @@ import org.mariotaku.twidere.graphic.drawable.userprofile.ActionBarDrawable
|
|||
import org.mariotaku.twidere.util.ThemeUtils
|
||||
import org.mariotaku.twidere.util.dagger.DependencyHolder
|
||||
|
||||
internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : AccessorHeaderBehavior<View>(context, attrs) {
|
||||
internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) :
|
||||
AccessorHeaderBehavior<ViewGroup>(context, attrs) {
|
||||
|
||||
var offsetDelta: Int = 0
|
||||
private set
|
||||
|
@ -47,48 +51,119 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : A
|
|||
private val cardBackgroundColor: Int
|
||||
private var tabItemIsDark: Int = 0
|
||||
|
||||
private var lastNestedScrollingChild by weak<View>()
|
||||
private var nestedScrollTarget by weak<View>()
|
||||
|
||||
init {
|
||||
val preferences = DependencyHolder.get(context).preferences
|
||||
cardBackgroundColor = ThemeUtils.getCardBackgroundColor(context,
|
||||
preferences[themeBackgroundOptionKey], preferences[themeBackgroundAlphaKey])
|
||||
}
|
||||
|
||||
override fun onMeasureChild(parent: CoordinatorLayout, child: View,
|
||||
override fun onStartNestedScroll(parent: CoordinatorLayout, child: ViewGroup,
|
||||
directTargetChild: View, target: View, nestedScrollAxes: Int, type: Int): Boolean {
|
||||
// Return true if we're nested scrolling vertically, and we have scrollable children
|
||||
// and the scrolling view is big enough to scroll
|
||||
val started = (nestedScrollAxes and ViewCompat.SCROLL_AXIS_VERTICAL != 0
|
||||
&& child.hasScrollableChildren
|
||||
&& parent.height - directTargetChild.height <= child.height)
|
||||
|
||||
// A new nested scroll has started so clear out the previous ref
|
||||
lastNestedScrollingChild = null
|
||||
|
||||
return started
|
||||
}
|
||||
|
||||
override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: ViewGroup,
|
||||
target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
|
||||
if (dy != 0) {
|
||||
val min: Int
|
||||
val max: Int
|
||||
if (dy < 0) {
|
||||
// We're scrolling down
|
||||
min = -child.totalScrollRange
|
||||
max = min + child.downNestedPreScrollRange
|
||||
} else {
|
||||
// We're scrolling up
|
||||
min = -child.upNestedPreScrollRange
|
||||
max = 0
|
||||
}
|
||||
if (min != max) {
|
||||
consumed[1] = scrollAccessor(coordinatorLayout, child, dy, min, max)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: ViewGroup,
|
||||
target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int,
|
||||
type: Int) {
|
||||
nestedScrollTarget = target
|
||||
if (dyUnconsumed < 0) {
|
||||
// If the scrolling view is scrolling down but not consuming, it's probably be at
|
||||
// the top of it's content
|
||||
if (scrollAccessor(coordinatorLayout, child, dyUnconsumed, -child.downNestedScrollRange,
|
||||
0) == 0) {
|
||||
if (target is RecyclerView) {
|
||||
target.stopScroll()
|
||||
} else {
|
||||
ViewCompat.stopNestedScroll(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: ViewGroup, target: View,
|
||||
type: Int) {
|
||||
// Keep a reference to the previous nested scrolling child
|
||||
lastNestedScrollingChild = target
|
||||
}
|
||||
|
||||
override fun onMeasureChild(parent: CoordinatorLayout, child: ViewGroup,
|
||||
parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int,
|
||||
heightUsed: Int): Boolean {
|
||||
return parent.measureChildIgnoringInsets(child, parentWidthMeasureSpec, widthUsed,
|
||||
parentHeightMeasureSpec, heightUsed)
|
||||
}
|
||||
|
||||
override fun onLayoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int): Boolean {
|
||||
val result = super.onLayoutChild(parent, child, layoutDirection)
|
||||
override fun onLayoutChild(parent: CoordinatorLayout, child: ViewGroup, layoutDirection: Int): Boolean {
|
||||
val handled = super.onLayoutChild(parent, child, layoutDirection)
|
||||
updateTabColor(parent, child, topAndBottomOffset)
|
||||
return result
|
||||
return handled
|
||||
}
|
||||
|
||||
override fun layoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int) {
|
||||
override fun layoutChild(parent: CoordinatorLayout, child: ViewGroup, layoutDirection: Int) {
|
||||
child.layout(0, 0, child.measuredWidth, child.measuredHeight)
|
||||
}
|
||||
|
||||
override fun getScrollRangeForDragFling(view: View): Int {
|
||||
val parent = view.parent as CoordinatorLayout
|
||||
val toolbar = parent.findViewById<View>(R.id.toolbar)
|
||||
val toolbarTabs = parent.findViewById<View>(R.id.toolbarTabs)
|
||||
return view.height - toolbar.bottom - toolbarTabs.height
|
||||
override fun canDragView(view: ViewGroup): Boolean {
|
||||
// Else we'll use the default behaviour of seeing if it can scroll down
|
||||
val scrollingView = lastNestedScrollingChild
|
||||
return if (scrollingView != null) {
|
||||
// If we have a reference to a scrolling view, check it
|
||||
scrollingView.isShown && !scrollingView.canScrollVertically(-1)
|
||||
} else {
|
||||
// Otherwise we assume that the scrolling view hasn't been scrolled and can drag.
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMaxDragOffset(view: View): Int {
|
||||
val parent = view.parent as CoordinatorLayout
|
||||
val toolbar = parent.findViewById<View>(R.id.toolbar)
|
||||
val toolbarTabs = parent.findViewById<View>(R.id.toolbarTabs)
|
||||
return -(view.height - toolbar.bottom - toolbarTabs.height)
|
||||
override fun onFlingFinished(parent: CoordinatorLayout, layout: ViewGroup) {
|
||||
// At the end of a manual fling, check to see if we need to snap to the edge-child
|
||||
}
|
||||
|
||||
override fun setHeaderTopBottomOffset(parent: CoordinatorLayout, header: View, newOffset: Int, minOffset: Int, maxOffset: Int): Int {
|
||||
override fun getMaxDragOffset(view: ViewGroup): Int {
|
||||
return -view.downNestedScrollRange
|
||||
}
|
||||
|
||||
override fun getScrollRangeForDragFling(view: ViewGroup): Int {
|
||||
return view.totalScrollRange
|
||||
}
|
||||
|
||||
override fun setHeaderTopBottomOffset(parent: CoordinatorLayout, header: ViewGroup, newOffset: Int, minOffset: Int, maxOffset: Int): Int {
|
||||
val curOffset = topBottomOffsetForScrollingSibling
|
||||
var consumed = 0
|
||||
|
||||
if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
|
||||
if (minOffset != 0 && curOffset in minOffset..maxOffset) {
|
||||
// If we have some scrolling range, and we're currently within the min and max
|
||||
// offsets, calculate a new offset
|
||||
val clampedOffset = newOffset.coerceIn(minOffset, maxOffset)
|
||||
|
@ -110,6 +185,10 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : A
|
|||
return consumed
|
||||
}
|
||||
|
||||
override fun getTopBottomOffsetForScrollingSibling(): Int {
|
||||
return topAndBottomOffset + offsetDelta
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
private fun updateTabColor(parent: CoordinatorLayout, header: View, offset: Int) {
|
||||
val actionBarBackground = parent.toolbar.background as? ActionBarDrawable ?: return
|
||||
|
@ -146,6 +225,5 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : A
|
|||
this.tabItemIsDark = tabItemIsDark
|
||||
}
|
||||
|
||||
override fun canDragView(view: View) = true
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.view.behavior.userprofile
|
||||
|
||||
import android.support.design.widget.CoordinatorLayout
|
||||
import android.view.View
|
||||
import kotlinx.android.synthetic.main.fragment_user.view.*
|
||||
import kotlinx.android.synthetic.main.header_user.view.*
|
||||
|
||||
internal val View.hasScrollableChildren: Boolean
|
||||
get() = totalScrollRange != 0
|
||||
|
||||
internal val View.totalScrollRange: Int
|
||||
get() {
|
||||
val parent = parent as CoordinatorLayout
|
||||
val toolbar = parent.toolbar
|
||||
val toolbarTabs = parent.toolbarTabs
|
||||
return toolbarTabs.top - toolbar.bottom
|
||||
}
|
||||
|
||||
internal val View.downNestedScrollRange: Int
|
||||
get() {
|
||||
val parent = parent as CoordinatorLayout
|
||||
val toolbar = parent.toolbar
|
||||
val toolbarTabs = parent.toolbarTabs
|
||||
return toolbarTabs.top - toolbar.bottom
|
||||
}
|
||||
|
||||
internal val View.downNestedPreScrollRange: Int
|
||||
get() = 0
|
||||
internal val View.upNestedPreScrollRange: Int
|
||||
get() = totalScrollRange
|
|
@ -22,20 +22,16 @@ package org.mariotaku.twidere.view.behavior.userprofile
|
|||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.support.design.widget.AccessorHeaderScrollingViewBehavior
|
||||
import android.support.design.widget.AppBarLayout
|
||||
import android.support.design.widget.CoordinatorLayout
|
||||
import android.support.v4.view.ViewCompat
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import kotlinx.android.synthetic.main.header_user.view.*
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.extension.view.measureChildIgnoringInsets
|
||||
|
||||
internal class PagerBehavior(context: Context, attrs: AttributeSet? = null) : AccessorHeaderScrollingViewBehavior(context, attrs) {
|
||||
|
||||
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
|
||||
return dependency.id == R.id.profileHeader
|
||||
}
|
||||
|
||||
override fun onMeasureChild(parent: CoordinatorLayout, child: View,
|
||||
parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int,
|
||||
heightUsed: Int): Boolean {
|
||||
|
@ -43,6 +39,10 @@ internal class PagerBehavior(context: Context, attrs: AttributeSet? = null) : Ac
|
|||
parentHeightMeasureSpec, heightUsed)
|
||||
}
|
||||
|
||||
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
|
||||
return dependency.id == R.id.profileHeader
|
||||
}
|
||||
|
||||
|
||||
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
|
||||
offsetChildAsNeeded(parent, child, dependency)
|
||||
|
@ -51,7 +51,7 @@ internal class PagerBehavior(context: Context, attrs: AttributeSet? = null) : Ac
|
|||
|
||||
override fun onRequestChildRectangleOnScreen(parent: CoordinatorLayout, child: View,
|
||||
rectangle: Rect, immediate: Boolean): Boolean {
|
||||
val header = findFirstDependency(parent.getDependencies(child)) ?: return false
|
||||
val header = parent.getDependencies(child).first()
|
||||
// Offset the rect by the child's left/top
|
||||
rectangle.offset(child.left, child.top)
|
||||
|
||||
|
@ -72,33 +72,30 @@ internal class PagerBehavior(context: Context, attrs: AttributeSet? = null) : Ac
|
|||
// Offset the child, pinning it to the bottom the header-dependency, maintaining
|
||||
// any vertical gap and overlap
|
||||
|
||||
ViewCompat.offsetTopAndBottom(child, (dependency.bottom - child.top
|
||||
ViewCompat.offsetTopAndBottom(child, (dependency.contentBottom - child.top
|
||||
+ behavior.offsetDelta + verticalLayoutGapAccessor)
|
||||
- getOverlapPixelsForOffsetAccessor(dependency))
|
||||
}
|
||||
|
||||
override fun getOverlapRatioForOffset(header: View): Float {
|
||||
// if (header is AppBarLayout) {
|
||||
// val abl = header as AppBarLayout?
|
||||
// val totalScrollRange = abl!!.totalScrollRange
|
||||
// val preScrollDown = abl.getDownNestedPreScrollRange()
|
||||
// val offset = getAppBarLayoutOffset(abl)
|
||||
//
|
||||
// if (preScrollDown != 0 && totalScrollRange + offset <= preScrollDown) {
|
||||
// // If we're in a pre-scroll down. Don't use the offset at all.
|
||||
// return 0f
|
||||
// } else {
|
||||
// val availScrollRange = totalScrollRange - preScrollDown
|
||||
// if (availScrollRange != 0) {
|
||||
// // Else we'll use a interpolated ratio of the overlap, depending on offset
|
||||
// return 1f + offset / availScrollRange.toFloat()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
val totalScrollRange = header.totalScrollRange
|
||||
val preScrollDown = header.downNestedPreScrollRange
|
||||
val offset = getAppBarLayoutOffset(header)
|
||||
|
||||
if (preScrollDown != 0 && totalScrollRange + offset <= preScrollDown) {
|
||||
// If we're in a pre-scroll down. Don't use the offset at all.
|
||||
return 0f
|
||||
} else {
|
||||
val availScrollRange = totalScrollRange - preScrollDown
|
||||
if (availScrollRange != 0) {
|
||||
// Else we'll use a interpolated ratio of the overlap, depending on offset
|
||||
return 1f + offset / availScrollRange.toFloat()
|
||||
}
|
||||
}
|
||||
return 0f
|
||||
}
|
||||
|
||||
private fun getAppBarLayoutOffset(abl: AppBarLayout): Int {
|
||||
private fun getAppBarLayoutOffset(abl: View): Int {
|
||||
val lp = abl.layoutParams as CoordinatorLayout.LayoutParams
|
||||
val behavior = lp.behavior as? HeaderBehavior ?: return 0
|
||||
return behavior.topBottomOffsetForScrollingSibling
|
||||
|
@ -109,7 +106,10 @@ internal class PagerBehavior(context: Context, attrs: AttributeSet? = null) : Ac
|
|||
}
|
||||
|
||||
override fun getScrollRange(v: View): Int {
|
||||
return (v as? AppBarLayout)?.totalScrollRange ?: super.getScrollRange(v)
|
||||
if (v.id == R.id.profileHeader) return v.totalScrollRange
|
||||
return super.getScrollRange(v)
|
||||
}
|
||||
|
||||
private val View.contentBottom
|
||||
get() = top + toolbarTabs.bottom
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.view.userprofile
|
||||
|
||||
import android.content.Context
|
||||
import android.support.design.widget.CoordinatorLayout
|
||||
import android.util.AttributeSet
|
||||
import org.mariotaku.chameleon.ChameleonView
|
||||
|
||||
class UserProfileCoordinatorLayout(context: Context, attrs: AttributeSet?) :
|
||||
CoordinatorLayout(context, attrs), ChameleonView.StatusBarThemeable {
|
||||
override fun isStatusBarColorHandled() = true
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
<org.mariotaku.twidere.view.userprofile.UserProfileCoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
|
@ -26,7 +26,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
app:statusBarBackground="@android:color/transparent"
|
||||
app:statusBarBackground="@null"
|
||||
tools:theme="@style/Theme.Twidere.NoActionBar">
|
||||
|
||||
<org.mariotaku.twidere.view.ExtendedViewPager
|
||||
|
@ -119,6 +119,7 @@
|
|||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/statusBarBackground"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="org.mariotaku.twidere.view.behavior.userprofile.StatusBarBehavior"
|
||||
tools:layout_height="25dp"/>
|
||||
|
@ -128,7 +129,6 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:layout_alignParentTop="true"
|
||||
android:elevation="@dimen/toolbar_elevation"
|
||||
app:layout_behavior="org.mariotaku.twidere.view.behavior.userprofile.ToolbarBehavior"
|
||||
app:popupTheme="?actionBarPopupTheme"
|
||||
tools:background="@drawable/shadow_bottom"
|
||||
|
@ -144,4 +144,4 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:background="?android:windowContentOverlay"/>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</org.mariotaku.twidere.view.userprofile.UserProfileCoordinatorLayout>
|
|
@ -23,11 +23,8 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/headerUserProfile"
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.mariotaku.twidere.view.ProfileBannerSpace
|
||||
android:id="@+id/profileBannerSpace"
|
||||
|
@ -305,6 +302,7 @@
|
|||
android:id="@+id/toolbarTabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/element_size_normal"
|
||||
android:outlineProvider="bounds"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
@ -315,7 +313,9 @@
|
|||
app:tabShowDivider="false"
|
||||
tools:background="?cardBackgroundColor"
|
||||
tools:ignore="UnusedAttribute"
|
||||
tools:listitem="@layout/layout_tab_item"/>
|
||||
tools:layoutManager="android.support.v7.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/layout_tab_item"
|
||||
tools:orientation="horizontal"/>
|
||||
|
||||
<org.mariotaku.twidere.view.ProfileImageView
|
||||
android:id="@+id/profileImage"
|
||||
|
|
|
@ -113,10 +113,6 @@
|
|||
<attr name="sivDrawShadow" format="boolean"/>
|
||||
<attr name="sivShape"/>
|
||||
</declare-styleable>
|
||||
<declare-styleable name="HeaderDrawerLayout">
|
||||
<attr name="hdl_headerLayout" format="reference"/>
|
||||
<attr name="hdl_contentLayout" format="reference"/>
|
||||
</declare-styleable>
|
||||
<declare-styleable name="TintedStatusLayout">
|
||||
<attr name="setPadding" format="boolean"/>
|
||||
</declare-styleable>
|
||||
|
|
Loading…
Reference in New Issue