mirror of
https://github.com/Tencent/QMUI_Android.git
synced 2026-10-18 21:21:01 +08:00
draggable scroll bar
This commit is contained in:
+1
@@ -49,6 +49,7 @@ public class QMUIContinuousNestedBottomRecyclerView extends RecyclerView
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setVerticalScrollBarEnabled(false);
|
||||
addOnScrollListener(new OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
|
||||
+72
-5
@@ -18,6 +18,7 @@ package com.qmuiteam.qmui.nestedScroll;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -32,7 +33,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
|
||||
public class QMUIContinuousNestedScrollLayout extends CoordinatorLayout implements
|
||||
QMUIContinuousNestedTopAreaBehavior.Callback {
|
||||
QMUIContinuousNestedTopAreaBehavior.Callback, QMUIDraggableScrollBar.Callback {
|
||||
|
||||
private IQMUIContinuousNestedTopView mTopView;
|
||||
private IQMUIContinuousNestedBottomView mBottomView;
|
||||
@@ -47,19 +48,80 @@ public class QMUIContinuousNestedScrollLayout extends CoordinatorLayout implemen
|
||||
}
|
||||
};
|
||||
private boolean mKeepBottomAreaStableWhenCheckLayout = false;
|
||||
private QMUIDraggableScrollBar mDraggableScrollBar;
|
||||
private boolean mIsDraggableScrollBarEnabled = false;
|
||||
|
||||
public QMUIContinuousNestedScrollLayout(@NonNull Context context) {
|
||||
super(context);
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public QMUIContinuousNestedScrollLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public QMUIContinuousNestedScrollLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
private void ensureScrollBar(){
|
||||
if(mDraggableScrollBar == null){
|
||||
mDraggableScrollBar = createScrollBar(getContext());
|
||||
mDraggableScrollBar.setCallback(this);
|
||||
CoordinatorLayout.LayoutParams lp = new CoordinatorLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
lp.gravity = Gravity.RIGHT;
|
||||
addView(mDraggableScrollBar, lp);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDraggableScrollBarEnabled(boolean draggableScrollBarEnabled) {
|
||||
mIsDraggableScrollBarEnabled = draggableScrollBarEnabled;
|
||||
}
|
||||
|
||||
protected QMUIDraggableScrollBar createScrollBar(Context context){
|
||||
return new QMUIDraggableScrollBar(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragToPercent(float percent) {
|
||||
int targetScroll = (int) (getScrollRange() * percent);
|
||||
scrollBy(targetScroll - getCurrentScroll());
|
||||
}
|
||||
|
||||
public int getCurrentScroll() {
|
||||
int currentScroll = 0;
|
||||
if (mTopView != null) {
|
||||
currentScroll += mTopView.getCurrentScroll();
|
||||
}
|
||||
currentScroll += getOffsetCurrent();
|
||||
if (mBottomView != null) {
|
||||
currentScroll += mBottomView.getCurrentScroll();
|
||||
}
|
||||
return currentScroll;
|
||||
}
|
||||
|
||||
public int getScrollRange() {
|
||||
int totalRange = 0;
|
||||
if (mTopView != null) {
|
||||
totalRange += mTopView.getScrollOffsetRange();
|
||||
}
|
||||
totalRange += getOffsetRange();
|
||||
|
||||
if (mBottomView != null) {
|
||||
totalRange += mBottomView.getScrollOffsetRange();
|
||||
}
|
||||
return totalRange;
|
||||
}
|
||||
|
||||
public float getCurrentScrollPercent() {
|
||||
int scrollRange = getScrollRange();
|
||||
if (scrollRange == 0) {
|
||||
return 0;
|
||||
}
|
||||
return getCurrentScroll() * 1f / scrollRange;
|
||||
}
|
||||
|
||||
|
||||
public void addOnScrollListener(@NonNull OnScrollListener onScrollListener) {
|
||||
if (!mOnScrollListeners.contains(onScrollListener)) {
|
||||
mOnScrollListeners.add(onScrollListener);
|
||||
@@ -112,7 +174,7 @@ public class QMUIContinuousNestedScrollLayout extends CoordinatorLayout implemen
|
||||
layoutParams.setBehavior(mTopAreaBehavior);
|
||||
}
|
||||
mTopAreaBehavior.setCallback(this);
|
||||
addView(topView, layoutParams);
|
||||
addView(topView, 0, layoutParams);
|
||||
}
|
||||
|
||||
public IQMUIContinuousNestedTopView getTopView() {
|
||||
@@ -164,7 +226,7 @@ public class QMUIContinuousNestedScrollLayout extends CoordinatorLayout implemen
|
||||
mBottomAreaBehavior = new QMUIContinuousNestedBottomAreaBehavior();
|
||||
layoutParams.setBehavior(mBottomAreaBehavior);
|
||||
}
|
||||
addView(bottomView, layoutParams);
|
||||
addView(bottomView, 0, layoutParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -229,6 +291,11 @@ public class QMUIContinuousNestedScrollLayout extends CoordinatorLayout implemen
|
||||
private void dispatchScroll(int topCurrent, int topRange,
|
||||
int offsetCurrent, int offsetRange,
|
||||
int bottomCurrent, int bottomRange) {
|
||||
if(mIsDraggableScrollBarEnabled){
|
||||
ensureScrollBar();
|
||||
mDraggableScrollBar.setPercent(getCurrentScrollPercent());
|
||||
mDraggableScrollBar.awakenScrollBar();
|
||||
}
|
||||
for (OnScrollListener onScrollListener : mOnScrollListeners) {
|
||||
onScrollListener.onScroll(topCurrent, topRange, offsetCurrent, offsetRange,
|
||||
bottomCurrent, bottomRange);
|
||||
|
||||
+7
@@ -33,14 +33,21 @@ public class QMUIContinuousNestedTopRecyclerView extends RecyclerView implements
|
||||
|
||||
public QMUIContinuousNestedTopRecyclerView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
init();
|
||||
}
|
||||
|
||||
public QMUIContinuousNestedTopRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
init();
|
||||
}
|
||||
|
||||
public QMUIContinuousNestedTopRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init(){
|
||||
setVerticalScrollBarEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,14 +29,21 @@ public class QMUIContinuousNestedTopWebView extends QMUIWebView implements IQMUI
|
||||
|
||||
public QMUIContinuousNestedTopWebView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public QMUIContinuousNestedTopWebView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public QMUIContinuousNestedTopWebView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init(){
|
||||
setVerticalScrollBarEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.qmuiteam.qmui.nestedScroll;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.qmuiteam.qmui.R;
|
||||
import com.qmuiteam.qmui.util.QMUILangHelper;
|
||||
|
||||
public class QMUIDraggableScrollBar extends View {
|
||||
|
||||
private Drawable mDragDrawable;
|
||||
private int mKeepShownTime = 800;
|
||||
private int mTransitionDuration = 100;
|
||||
private long mStartTransitionTime = 0;
|
||||
private float mCurrentAlpha = 0f;
|
||||
private float mPercent = 0f;
|
||||
private Runnable mDelayInvalidateRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
invalidate();
|
||||
}
|
||||
};
|
||||
private boolean mIsInDragging = false;
|
||||
private Callback mCallback;
|
||||
|
||||
public QMUIDraggableScrollBar(Context context) {
|
||||
this(context, (AttributeSet) null);
|
||||
}
|
||||
|
||||
public QMUIDraggableScrollBar(Context context, @NonNull Drawable dragDrawable) {
|
||||
this(context, (AttributeSet) null);
|
||||
mDragDrawable = dragDrawable.mutate();
|
||||
}
|
||||
|
||||
public QMUIDraggableScrollBar(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public void setCallback(Callback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public void setKeepShownTime(int keepShownTime) {
|
||||
mKeepShownTime = keepShownTime;
|
||||
}
|
||||
|
||||
public void setTransitionDuration(int transitionDuration) {
|
||||
mTransitionDuration = transitionDuration;
|
||||
}
|
||||
|
||||
public void setDragDrawable(Drawable dragDrawable) {
|
||||
mDragDrawable = dragDrawable.mutate();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setPercent(float percent) {
|
||||
mPercent = percent;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
Drawable drawable = mDragDrawable;
|
||||
if (drawable == null) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
return;
|
||||
}
|
||||
super.onMeasure(MeasureSpec.makeMeasureSpec(
|
||||
drawable.getIntrinsicWidth(), MeasureSpec.EXACTLY), heightMeasureSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
Drawable drawable = mDragDrawable;
|
||||
if (drawable == null) {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
int action = event.getAction();
|
||||
final float x = event.getX();
|
||||
final float y = event.getY();
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
mIsInDragging = false;
|
||||
float drawableY = mPercent * (getHeight() - drawable.getIntrinsicHeight());
|
||||
if (mCurrentAlpha > 0 && x > getWidth() - drawable.getIntrinsicWidth()
|
||||
&& y >= drawableY && y <= drawableY + drawable.getIntrinsicHeight()) {
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
mIsInDragging = true;
|
||||
}
|
||||
} else if (action == MotionEvent.ACTION_MOVE) {
|
||||
if (mIsInDragging) {
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
onDragging(drawable, y);
|
||||
}
|
||||
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
if (mIsInDragging) {
|
||||
mIsInDragging = false;
|
||||
onDragging(drawable, y);
|
||||
}
|
||||
}
|
||||
return mIsInDragging;
|
||||
}
|
||||
|
||||
private void onDragging(Drawable drawable, float currentY) {
|
||||
float percent = currentY / (getHeight() - drawable.getIntrinsicHeight());
|
||||
percent = QMUILangHelper.constrain(percent, 0f, 1f);
|
||||
if (mCallback != null) {
|
||||
mCallback.onDragToPercent(percent);
|
||||
}
|
||||
setPercent(percent);
|
||||
}
|
||||
|
||||
public void awakenScrollBar() {
|
||||
if (mDragDrawable == null) {
|
||||
mDragDrawable = ContextCompat.getDrawable(getContext(), R.drawable.qmui_icon_scroll_bar);
|
||||
}
|
||||
long current = System.currentTimeMillis();
|
||||
if (current - mStartTransitionTime > mTransitionDuration) {
|
||||
mStartTransitionTime = current - mTransitionDuration;
|
||||
}
|
||||
ViewCompat.postInvalidateOnAnimation(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
Drawable drawable = mDragDrawable;
|
||||
if (drawable == null) {
|
||||
return;
|
||||
}
|
||||
int drawableWidth = drawable.getIntrinsicWidth();
|
||||
int drawableHeight = drawable.getIntrinsicHeight();
|
||||
if (drawableWidth <= 0 || drawableHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
long timeAfterStartShow = System.currentTimeMillis() - mStartTransitionTime;
|
||||
Log.i("cgine", "timeAfterStartShow = " + timeAfterStartShow);
|
||||
long timeAfterEndShow;
|
||||
int needInvalidate = -1;
|
||||
if (timeAfterStartShow < mTransitionDuration) {
|
||||
// in show animation
|
||||
mCurrentAlpha = timeAfterStartShow * 1f / mTransitionDuration;
|
||||
needInvalidate = 0;
|
||||
} else if (timeAfterStartShow - mTransitionDuration < mKeepShownTime) {
|
||||
// keep show
|
||||
mCurrentAlpha = 1f;
|
||||
needInvalidate = (int) (mKeepShownTime - (timeAfterStartShow - mTransitionDuration));
|
||||
} else if ((timeAfterEndShow = timeAfterStartShow - mTransitionDuration - mKeepShownTime)
|
||||
< mTransitionDuration) {
|
||||
// in hide animation
|
||||
mCurrentAlpha = 1 - timeAfterEndShow * 1f / mTransitionDuration;
|
||||
needInvalidate = 0;
|
||||
} else {
|
||||
mCurrentAlpha = 0f;
|
||||
}
|
||||
if (mCurrentAlpha <= 0f) {
|
||||
return;
|
||||
}
|
||||
drawable.setAlpha((int) (mCurrentAlpha * 255));
|
||||
int totalHeight = getHeight(), totalWidth = getWidth();
|
||||
int top = (int) ((totalHeight - drawableHeight) * mPercent);
|
||||
int left = totalWidth - drawableWidth;
|
||||
drawable.setBounds(0, 0, drawableWidth, drawableHeight);
|
||||
canvas.save();
|
||||
canvas.translate(left, top);
|
||||
drawable.draw(canvas);
|
||||
canvas.restore();
|
||||
|
||||
if (needInvalidate == 0) {
|
||||
invalidate();
|
||||
} else if (needInvalidate > 0) {
|
||||
ViewCompat.postOnAnimationDelayed(this, mDelayInvalidateRunnable, needInvalidate);
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
void onDragToPercent(float percent);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
Reference in New Issue
Block a user