draggable scroll bar

This commit is contained in:
cgspine
2019-05-30 16:47:04 +08:00
parent c525459098
commit b7e4e09edd
8 changed files with 274 additions and 5 deletions
@@ -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) {
@@ -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);
@@ -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