升级 Java 版本

将 zhanghai/AndroidFastScroll 添加为子模块以确保 Java 版本的升级(修复资源引用错误,未做其它改动)
This commit is contained in:
Super12138 2025-01-01 11:28:20 +08:00
parent eb2adf5479
commit d3249dd8a7
34 changed files with 2641 additions and 9 deletions

View file

@ -53,11 +53,11 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_18
targetCompatibility = JavaVersion.VERSION_18
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "18"
}
buildFeatures {
viewBinding = true
@ -89,10 +89,8 @@ dependencies {
implementation(libs.androidx.room.ktx)
annotationProcessor(libs.androidx.room.compiler)
ksp(libs.androidx.room.compiler)
// Room Backup
// implementation(libs.room.backup)
// Fast Scroll
implementation(libs.fast.scroll)
// FastScroll
implementation(project(":fastscroll"))
// Test
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)

View file

@ -9,7 +9,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

View file

@ -3,4 +3,5 @@ plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.android.library) apply false
}

1
fastscroll/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,42 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "me.zhanghai.android.fastscroll"
compileSdk = 34
defaultConfig {
minSdk = 21
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_18
targetCompatibility = JavaVersion.VERSION_18
}
kotlinOptions {
jvmTarget = "18"
}
}
dependencies {
implementation(libs.androidx.appcompat)
implementation(libs.androidx.recyclerview)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}

View file

21
fastscroll/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<manifest />

View file

@ -0,0 +1,74 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.annotation.SuppressLint;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.graphics.drawable.DrawableWrapperCompat;
import androidx.core.graphics.drawable.DrawableCompat;
@SuppressLint("RestrictedApi")
class AutoMirrorDrawable extends DrawableWrapperCompat {
public AutoMirrorDrawable(@NonNull Drawable drawable) {
super(drawable);
}
@Override
public void draw(@NonNull Canvas canvas) {
if (needMirroring()) {
float centerX = getBounds().exactCenterX();
canvas.scale(-1, 1, centerX, 0);
super.draw(canvas);
canvas.scale(-1, 1, centerX, 0);
} else {
super.draw(canvas);
}
}
@Override
public boolean onLayoutDirectionChanged(int layoutDirection) {
super.onLayoutDirectionChanged(layoutDirection);
return true;
}
@Override
public boolean isAutoMirrored() {
return true;
}
private boolean needMirroring() {
return DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL;
}
@Override
public boolean getPadding(@NonNull Rect padding) {
boolean hasPadding = super.getPadding(padding);
if (needMirroring()) {
int paddingStart = padding.left;
int paddingEnd = padding.right;
padding.left = paddingEnd;
padding.right = paddingStart;
}
return hasPadding;
}
}

View file

@ -0,0 +1,141 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.view.View;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
public class DefaultAnimationHelper implements FastScroller.AnimationHelper {
private static final int SHOW_DURATION_MILLIS = 150;
private static final int HIDE_DURATION_MILLIS = 200;
private static final Interpolator SHOW_SCROLLBAR_INTERPOLATOR =
new LinearOutSlowInInterpolator();
private static final Interpolator HIDE_SCROLLBAR_INTERPOLATOR =
new FastOutLinearInInterpolator();
private static final int AUTO_HIDE_SCROLLBAR_DELAY_MILLIS = 1500;
@NonNull
private final View mView;
private boolean mScrollbarAutoHideEnabled = true;
private boolean mShowingScrollbar = true;
private boolean mShowingPopup;
public DefaultAnimationHelper(@NonNull View view) {
mView = view;
}
@Override
public void showScrollbar(@NonNull View trackView, @NonNull View thumbView) {
if (mShowingScrollbar) {
return;
}
mShowingScrollbar = true;
trackView.animate()
.alpha(1)
.translationX(0)
.setDuration(SHOW_DURATION_MILLIS)
.setInterpolator(SHOW_SCROLLBAR_INTERPOLATOR)
.start();
thumbView.animate()
.alpha(1)
.translationX(0)
.setDuration(SHOW_DURATION_MILLIS)
.setInterpolator(SHOW_SCROLLBAR_INTERPOLATOR)
.start();
}
@Override
public void hideScrollbar(@NonNull View trackView, @NonNull View thumbView) {
if (!mShowingScrollbar) {
return;
}
mShowingScrollbar = false;
boolean isLayoutRtl = mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
int width = Math.max(trackView.getWidth(), thumbView.getWidth());
float translationX;
if (isLayoutRtl) {
translationX = trackView.getLeft() == 0 ? -width : 0;
} else {
translationX = trackView.getRight() == mView.getWidth() ? width : 0;
}
trackView.animate()
.alpha(0)
.translationX(translationX)
.setDuration(HIDE_DURATION_MILLIS)
.setInterpolator(HIDE_SCROLLBAR_INTERPOLATOR)
.start();
thumbView.animate()
.alpha(0)
.translationX(translationX)
.setDuration(HIDE_DURATION_MILLIS)
.setInterpolator(HIDE_SCROLLBAR_INTERPOLATOR)
.start();
}
@Override
public boolean isScrollbarAutoHideEnabled() {
return mScrollbarAutoHideEnabled;
}
public void setScrollbarAutoHideEnabled(boolean enabled) {
mScrollbarAutoHideEnabled = enabled;
}
@Override
public int getScrollbarAutoHideDelayMillis() {
return AUTO_HIDE_SCROLLBAR_DELAY_MILLIS;
}
@Override
public void showPopup(@NonNull View popupView) {
if (mShowingPopup) {
return;
}
mShowingPopup = true;
popupView.animate()
.alpha(1)
.setDuration(SHOW_DURATION_MILLIS)
.start();
}
@Override
public void hidePopup(@NonNull View popupView) {
if (!mShowingPopup) {
return;
}
mShowingPopup = false;
popupView.animate()
.alpha(0)
.setDuration(HIDE_DURATION_MILLIS)
.start();
}
}

View file

@ -0,0 +1,135 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.widget.NestedScrollView;
@SuppressLint("MissingSuperCall")
public class FastScrollNestedScrollView extends NestedScrollView implements ViewHelperProvider {
@NonNull
private final ViewHelper mViewHelper = new ViewHelper();
public FastScrollNestedScrollView(@NonNull Context context) {
super(context);
init();
}
public FastScrollNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public FastScrollNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setScrollContainer(true);
}
@NonNull
@Override
public FastScroller.ViewHelper getViewHelper() {
return mViewHelper;
}
@Override
public void draw(@NonNull Canvas canvas) {
mViewHelper.draw(canvas);
}
@Override
protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) {
mViewHelper.onScrollChanged(left, top, oldLeft, oldTop);
}
@Override
public boolean onInterceptTouchEvent(@NonNull MotionEvent event) {
return mViewHelper.onInterceptTouchEvent(event);
}
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouchEvent(@NonNull MotionEvent event) {
return mViewHelper.onTouchEvent(event);
}
private class ViewHelper extends SimpleViewHelper {
@Override
public int getScrollRange() {
return super.getScrollRange() + getPaddingTop() + getPaddingBottom();
}
@Override
protected void superDraw(@NonNull Canvas canvas) {
FastScrollNestedScrollView.super.draw(canvas);
}
@Override
protected void superOnScrollChanged(int left, int top, int oldLeft, int oldTop) {
FastScrollNestedScrollView.super.onScrollChanged(left, top, oldLeft, oldTop);
}
@Override
protected boolean superOnInterceptTouchEvent(@NonNull MotionEvent event) {
return FastScrollNestedScrollView.super.onInterceptTouchEvent(event);
}
@Override
protected boolean superOnTouchEvent(@NonNull MotionEvent event) {
return FastScrollNestedScrollView.super.onTouchEvent(event);
}
@Override
@SuppressLint("RestrictedApi")
protected int computeVerticalScrollRange() {
return FastScrollNestedScrollView.this.computeVerticalScrollRange();
}
@Override
@SuppressLint("RestrictedApi")
protected int computeVerticalScrollOffset() {
return FastScrollNestedScrollView.this.computeVerticalScrollOffset();
}
@Override
protected int getScrollX() {
return FastScrollNestedScrollView.this.getScrollX();
}
@Override
protected void scrollTo(int x, int y) {
FastScrollNestedScrollView.this.scrollTo(x, y);
}
}
}

View file

@ -0,0 +1,142 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ScrollView;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
@SuppressLint("MissingSuperCall")
public class FastScrollScrollView extends ScrollView implements ViewHelperProvider {
@NonNull
private final ViewHelper mViewHelper = new ViewHelper();
public FastScrollScrollView(@NonNull Context context) {
super(context);
init();
}
public FastScrollScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public FastScrollScrollView(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public FastScrollScrollView(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setVerticalScrollBarEnabled(false);
setScrollContainer(true);
}
@NonNull
@Override
public FastScroller.ViewHelper getViewHelper() {
return mViewHelper;
}
@Override
public void draw(@NonNull Canvas canvas) {
mViewHelper.draw(canvas);
}
@Override
protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) {
mViewHelper.onScrollChanged(left, top, oldLeft, oldTop);
}
@Override
public boolean onInterceptTouchEvent(@NonNull MotionEvent event) {
return mViewHelper.onInterceptTouchEvent(event);
}
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouchEvent(@NonNull MotionEvent event) {
return mViewHelper.onTouchEvent(event);
}
private class ViewHelper extends SimpleViewHelper {
@Override
public int getScrollRange() {
return super.getScrollRange() + getPaddingTop() + getPaddingBottom();
}
@Override
protected void superDraw(@NonNull Canvas canvas) {
FastScrollScrollView.super.draw(canvas);
}
@Override
protected void superOnScrollChanged(int left, int top, int oldLeft, int oldTop) {
FastScrollScrollView.super.onScrollChanged(left, top, oldLeft, oldTop);
}
@Override
protected boolean superOnInterceptTouchEvent(@NonNull MotionEvent event) {
return FastScrollScrollView.super.onInterceptTouchEvent(event);
}
@Override
protected boolean superOnTouchEvent(@NonNull MotionEvent event) {
return FastScrollScrollView.super.onTouchEvent(event);
}
@Override
protected int computeVerticalScrollRange() {
return FastScrollScrollView.this.computeVerticalScrollRange();
}
@Override
protected int computeVerticalScrollOffset() {
return FastScrollScrollView.this.computeVerticalScrollOffset();
}
@Override
protected int getScrollX() {
return FastScrollScrollView.this.getScrollX();
}
@Override
protected void scrollTo(int x, int y) {
FastScrollScrollView.this.scrollTo(x, y);
}
}
}

View file

@ -0,0 +1,137 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.webkit.WebView;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
@SuppressLint("MissingSuperCall")
public class FastScrollWebView extends WebView implements ViewHelperProvider {
@NonNull
private final ViewHelper mViewHelper = new ViewHelper();
public FastScrollWebView(@NonNull Context context) {
super(context);
init();
}
public FastScrollWebView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public FastScrollWebView(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public FastScrollWebView(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setVerticalScrollBarEnabled(false);
setScrollContainer(true);
}
@NonNull
@Override
public FastScroller.ViewHelper getViewHelper() {
return mViewHelper;
}
@Override
public void draw(@NonNull Canvas canvas) {
mViewHelper.draw(canvas);
}
@Override
protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) {
mViewHelper.onScrollChanged(left, top, oldLeft, oldTop);
}
@Override
public boolean onInterceptTouchEvent(@NonNull MotionEvent event) {
return mViewHelper.onInterceptTouchEvent(event);
}
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouchEvent(@NonNull MotionEvent event) {
return mViewHelper.onTouchEvent(event);
}
private class ViewHelper extends SimpleViewHelper {
@Override
protected void superDraw(@NonNull Canvas canvas) {
FastScrollWebView.super.draw(canvas);
}
@Override
protected void superOnScrollChanged(int left, int top, int oldLeft, int oldTop) {
FastScrollWebView.super.onScrollChanged(left, top, oldLeft, oldTop);
}
@Override
protected boolean superOnInterceptTouchEvent(@NonNull MotionEvent event) {
return FastScrollWebView.super.onInterceptTouchEvent(event);
}
@Override
protected boolean superOnTouchEvent(@NonNull MotionEvent event) {
return FastScrollWebView.super.onTouchEvent(event);
}
@Override
protected int computeVerticalScrollRange() {
return FastScrollWebView.this.computeVerticalScrollRange();
}
@Override
protected int computeVerticalScrollOffset() {
return FastScrollWebView.this.computeVerticalScrollOffset();
}
@Override
protected int getScrollX() {
return FastScrollWebView.this.getScrollX();
}
@Override
protected void scrollTo(int x, int y) {
FastScrollWebView.this.scrollTo(x, y);
}
}
}

View file

@ -0,0 +1,465 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewGroupOverlay;
import android.widget.FrameLayout;
import android.widget.TextView;
import java.util.Objects;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.core.math.MathUtils;
import androidx.core.util.Consumer;
public class FastScroller {
private final int mMinTouchTargetSize;
private final int mTouchSlop;
@NonNull
private final ViewGroup mView;
@NonNull
private final ViewHelper mViewHelper;
@Nullable
private Rect mUserPadding;
@NonNull
private final AnimationHelper mAnimationHelper;
private final int mTrackWidth;
private final int mThumbWidth;
private final int mThumbHeight;
@NonNull
private final View mTrackView;
@NonNull
private final View mThumbView;
@NonNull
private final TextView mPopupView;
private boolean mScrollbarEnabled;
private int mThumbOffset;
private float mDownX;
private float mDownY;
private float mLastY;
private float mDragStartY;
private int mDragStartThumbOffset;
private boolean mDragging;
@NonNull
private final Runnable mAutoHideScrollbarRunnable = this::autoHideScrollbar;
@NonNull
private final Rect mTempRect = new Rect();
public FastScroller(@NonNull ViewGroup view, @NonNull ViewHelper viewHelper,
@Nullable Rect padding, @NonNull Drawable trackDrawable,
@NonNull Drawable thumbDrawable, @NonNull Consumer<TextView> popupStyle,
@NonNull AnimationHelper animationHelper) {
mMinTouchTargetSize = view.getResources().getDimensionPixelSize(
R.dimen.afs_min_touch_target_size);
Context context = view.getContext();
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mView = view;
mViewHelper = viewHelper;
mUserPadding = padding;
mAnimationHelper = animationHelper;
mTrackWidth = requireNonNegative(trackDrawable.getIntrinsicWidth(),
"trackDrawable.getIntrinsicWidth() < 0");
mThumbWidth = requireNonNegative(thumbDrawable.getIntrinsicWidth(),
"thumbDrawable.getIntrinsicWidth() < 0");
mThumbHeight = requireNonNegative(thumbDrawable.getIntrinsicHeight(),
"thumbDrawable.getIntrinsicHeight() < 0");
mTrackView = new View(context);
mTrackView.setBackground(trackDrawable);
mThumbView = new View(context);
mThumbView.setBackground(thumbDrawable);
mPopupView = new AppCompatTextView(context);
mPopupView.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
popupStyle.accept(mPopupView);
ViewGroupOverlay overlay = mView.getOverlay();
overlay.add(mTrackView);
overlay.add(mThumbView);
overlay.add(mPopupView);
postAutoHideScrollbar();
mPopupView.setAlpha(0);
mViewHelper.addOnPreDrawListener(this::onPreDraw);
mViewHelper.addOnScrollChangedListener(this::onScrollChanged);
mViewHelper.addOnTouchEventListener(this::onTouchEvent);
}
private static int requireNonNegative(int value, @NonNull String message) {
if (value < 0) {
throw new IllegalArgumentException(message);
}
return value;
}
public void setPadding(int left, int top, int right, int bottom) {
if (mUserPadding != null && mUserPadding.left == left && mUserPadding.top == top
&& mUserPadding.right == right && mUserPadding.bottom == bottom) {
return;
}
if (mUserPadding == null) {
mUserPadding = new Rect();
}
mUserPadding.set(left, top, right, bottom);
mView.invalidate();
}
public void setPadding(@Nullable Rect padding) {
if (Objects.equals(mUserPadding, padding)) {
return;
}
if (padding != null) {
if (mUserPadding == null) {
mUserPadding = new Rect();
}
mUserPadding.set(padding);
} else {
mUserPadding = null;
}
mView.invalidate();
}
@NonNull
private Rect getPadding() {
if (mUserPadding != null) {
mTempRect.set(mUserPadding);
} else {
mTempRect.set(mView.getPaddingLeft(), mView.getPaddingTop(), mView.getPaddingRight(),
mView.getPaddingBottom());
}
return mTempRect;
}
private void onPreDraw() {
updateScrollbarState();
mTrackView.setVisibility(mScrollbarEnabled ? View.VISIBLE : View.INVISIBLE);
mThumbView.setVisibility(mScrollbarEnabled ? View.VISIBLE : View.INVISIBLE);
if (!mScrollbarEnabled) {
mPopupView.setVisibility(View.INVISIBLE);
return;
}
int layoutDirection = mView.getLayoutDirection();
mTrackView.setLayoutDirection(layoutDirection);
mThumbView.setLayoutDirection(layoutDirection);
mPopupView.setLayoutDirection(layoutDirection);
boolean isLayoutRtl = layoutDirection == View.LAYOUT_DIRECTION_RTL;
int viewWidth = mView.getWidth();
int viewHeight = mView.getHeight();
Rect padding = getPadding();
int trackLeft = isLayoutRtl ? padding.left : viewWidth - padding.right - mTrackWidth;
layoutView(mTrackView, trackLeft, padding.top, trackLeft + mTrackWidth,
Math.max(viewHeight - padding.bottom, padding.top));
int thumbLeft = isLayoutRtl ? padding.left : viewWidth - padding.right - mThumbWidth;
int thumbTop = padding.top + mThumbOffset;
layoutView(mThumbView, thumbLeft, thumbTop, thumbLeft + mThumbWidth,
thumbTop + mThumbHeight);
CharSequence popupText = mViewHelper.getPopupText();
boolean hasPopup = !TextUtils.isEmpty(popupText);
mPopupView.setVisibility(hasPopup ? View.VISIBLE : View.INVISIBLE);
if (hasPopup) {
FrameLayout.LayoutParams popupLayoutParams = (FrameLayout.LayoutParams)
mPopupView.getLayoutParams();
if (!Objects.equals(mPopupView.getText(), popupText)) {
mPopupView.setText(popupText);
int widthMeasureSpec = ViewGroup.getChildMeasureSpec(
View.MeasureSpec.makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY),
padding.left + padding.right + mThumbWidth + popupLayoutParams.leftMargin
+ popupLayoutParams.rightMargin, popupLayoutParams.width);
int heightMeasureSpec = ViewGroup.getChildMeasureSpec(
View.MeasureSpec.makeMeasureSpec(viewHeight, View.MeasureSpec.EXACTLY),
padding.top + padding.bottom + popupLayoutParams.topMargin
+ popupLayoutParams.bottomMargin, popupLayoutParams.height);
mPopupView.measure(widthMeasureSpec, heightMeasureSpec);
}
int popupWidth = mPopupView.getMeasuredWidth();
int popupHeight = mPopupView.getMeasuredHeight();
int popupLeft = isLayoutRtl ? padding.left + mThumbWidth + popupLayoutParams.leftMargin
: viewWidth - padding.right - mThumbWidth - popupLayoutParams.rightMargin
- popupWidth;
int popupAnchorY;
switch (popupLayoutParams.gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
default:
popupAnchorY = 0;
break;
case Gravity.CENTER_HORIZONTAL:
popupAnchorY = popupHeight / 2;
break;
case Gravity.RIGHT:
popupAnchorY = popupHeight;
break;
}
int thumbAnchorY;
switch (popupLayoutParams.gravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
default:
thumbAnchorY = mThumbView.getPaddingTop();
break;
case Gravity.CENTER_VERTICAL: {
int thumbPaddingTop = mThumbView.getPaddingTop();
thumbAnchorY = thumbPaddingTop + (mThumbHeight - thumbPaddingTop
- mThumbView.getPaddingBottom()) / 2;
break;
}
case Gravity.BOTTOM:
thumbAnchorY = mThumbHeight - mThumbView.getPaddingBottom();
break;
}
int popupTop = MathUtils.clamp(thumbTop + thumbAnchorY - popupAnchorY,
padding.top + popupLayoutParams.topMargin,
viewHeight - padding.bottom - popupLayoutParams.bottomMargin - popupHeight);
layoutView(mPopupView, popupLeft, popupTop, popupLeft + popupWidth,
popupTop + popupHeight);
}
}
private void updateScrollbarState() {
int scrollOffsetRange = getScrollOffsetRange();
mScrollbarEnabled = scrollOffsetRange > 0;
mThumbOffset = mScrollbarEnabled ? (int) ((long) getThumbOffsetRange()
* mViewHelper.getScrollOffset() / scrollOffsetRange) : 0;
}
private void layoutView(@NonNull View view, int left, int top, int right, int bottom) {
int scrollX = mView.getScrollX();
int scrollY = mView.getScrollY();
view.layout(scrollX + left, scrollY + top, scrollX + right, scrollY + bottom);
}
private void onScrollChanged() {
updateScrollbarState();
if (!mScrollbarEnabled) {
return;
}
mAnimationHelper.showScrollbar(mTrackView, mThumbView);
postAutoHideScrollbar();
}
private boolean onTouchEvent(@NonNull MotionEvent event) {
if (!mScrollbarEnabled) {
return false;
}
float eventX = event.getX();
float eventY = event.getY();
Rect padding = getPadding();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = eventX;
mDownY = eventY;
if (mThumbView.getAlpha() > 0 && isInViewTouchTarget(mThumbView, eventX, eventY)) {
mDragStartY = eventY;
mDragStartThumbOffset = mThumbOffset;
setDragging(true);
}
break;
case MotionEvent.ACTION_MOVE:
if (!mDragging && isInViewTouchTarget(mTrackView, mDownX, mDownY)
&& Math.abs(eventY - mDownY) > mTouchSlop) {
if (isInViewTouchTarget(mThumbView, mDownX, mDownY)) {
mDragStartY = mLastY;
mDragStartThumbOffset = mThumbOffset;
} else {
mDragStartY = eventY;
mDragStartThumbOffset = (int) (eventY - padding.top - mThumbHeight / 2f);
scrollToThumbOffset(mDragStartThumbOffset);
}
setDragging(true);
}
if (mDragging) {
int thumbOffset = mDragStartThumbOffset + (int) (eventY - mDragStartY);
scrollToThumbOffset(thumbOffset);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
setDragging(false);
break;
}
mLastY = eventY;
return mDragging;
}
private boolean isInView(@NonNull View view, float x, float y) {
int scrollX = mView.getScrollX();
int scrollY = mView.getScrollY();
return x >= view.getLeft() - scrollX && x < view.getRight() - scrollX
&& y >= view.getTop() - scrollY && y < view.getBottom() - scrollY;
}
private boolean isInViewTouchTarget(@NonNull View view, float x, float y) {
int scrollX = mView.getScrollX();
int scrollY = mView.getScrollY();
return isInTouchTarget(x, view.getLeft() - scrollX, view.getRight() - scrollX, 0,
mView.getWidth())
&& isInTouchTarget(y, view.getTop() - scrollY, view.getBottom() - scrollY, 0,
mView.getHeight());
}
private boolean isInTouchTarget(float position, int viewStart, int viewEnd, int parentStart,
int parentEnd) {
int viewSize = viewEnd - viewStart;
if (viewSize >= mMinTouchTargetSize) {
return position >= viewStart && position < viewEnd;
}
int touchTargetStart = viewStart - (mMinTouchTargetSize - viewSize) / 2;
if (touchTargetStart < parentStart) {
touchTargetStart = parentStart;
}
int touchTargetEnd = touchTargetStart + mMinTouchTargetSize;
if (touchTargetEnd > parentEnd) {
touchTargetEnd = parentEnd;
touchTargetStart = touchTargetEnd - mMinTouchTargetSize;
if (touchTargetStart < parentStart) {
touchTargetStart = parentStart;
}
}
return position >= touchTargetStart && position < touchTargetEnd;
}
private void scrollToThumbOffset(int thumbOffset) {
int thumbOffsetRange = getThumbOffsetRange();
thumbOffset = MathUtils.clamp(thumbOffset, 0, thumbOffsetRange);
int scrollOffset = (int) ((long) getScrollOffsetRange() * thumbOffset / thumbOffsetRange);
mViewHelper.scrollTo(scrollOffset);
}
private int getScrollOffsetRange() {
return mViewHelper.getScrollRange() - mView.getHeight();
}
private int getThumbOffsetRange() {
Rect padding = getPadding();
return mView.getHeight() - padding.top - padding.bottom - mThumbHeight;
}
private void setDragging(boolean dragging) {
if (mDragging == dragging) {
return;
}
mDragging = dragging;
if (mDragging) {
mView.getParent().requestDisallowInterceptTouchEvent(true);
}
mTrackView.setPressed(mDragging);
mThumbView.setPressed(mDragging);
if (mDragging) {
cancelAutoHideScrollbar();
mAnimationHelper.showScrollbar(mTrackView, mThumbView);
mAnimationHelper.showPopup(mPopupView);
} else {
postAutoHideScrollbar();
mAnimationHelper.hidePopup(mPopupView);
}
}
private void postAutoHideScrollbar() {
cancelAutoHideScrollbar();
if (mAnimationHelper.isScrollbarAutoHideEnabled()) {
mView.postDelayed(mAutoHideScrollbarRunnable,
mAnimationHelper.getScrollbarAutoHideDelayMillis());
}
}
private void autoHideScrollbar() {
if (mDragging) {
return;
}
mAnimationHelper.hideScrollbar(mTrackView, mThumbView);
}
private void cancelAutoHideScrollbar() {
mView.removeCallbacks(mAutoHideScrollbarRunnable);
}
public interface ViewHelper {
void addOnPreDrawListener(@NonNull Runnable onPreDraw);
void addOnScrollChangedListener(@NonNull Runnable onScrollChanged);
void addOnTouchEventListener(@NonNull Predicate<MotionEvent> onTouchEvent);
int getScrollRange();
int getScrollOffset();
void scrollTo(int offset);
@Nullable
default CharSequence getPopupText() {
return null;
}
}
public interface AnimationHelper {
void showScrollbar(@NonNull View trackView, @NonNull View thumbView);
void hideScrollbar(@NonNull View trackView, @NonNull View thumbView);
boolean isScrollbarAutoHideEnabled();
int getScrollbarAutoHideDelayMillis();
void showPopup(@NonNull View popupView);
void hidePopup(@NonNull View popupView);
}
}

View file

@ -0,0 +1,188 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
import androidx.core.widget.NestedScrollView;
import androidx.recyclerview.widget.RecyclerView;
public class FastScrollerBuilder {
@NonNull
private final ViewGroup mView;
@Nullable
private FastScroller.ViewHelper mViewHelper;
@Nullable
private PopupTextProvider mPopupTextProvider;
@Nullable
private Rect mPadding;
@NonNull
private Drawable mTrackDrawable;
@NonNull
private Drawable mThumbDrawable;
@NonNull
private Consumer<TextView> mPopupStyle;
@Nullable
private FastScroller.AnimationHelper mAnimationHelper;
public FastScrollerBuilder(@NonNull ViewGroup view) {
mView = view;
useDefaultStyle();
}
@NonNull
public FastScrollerBuilder setViewHelper(@Nullable FastScroller.ViewHelper viewHelper) {
mViewHelper = viewHelper;
return this;
}
@NonNull
public FastScrollerBuilder setPopupTextProvider(@Nullable PopupTextProvider popupTextProvider) {
mPopupTextProvider = popupTextProvider;
return this;
}
@NonNull
public FastScrollerBuilder setPadding(int left, int top, int right, int bottom) {
if (mPadding == null) {
mPadding = new Rect();
}
mPadding.set(left, top, right, bottom);
return this;
}
@NonNull
public FastScrollerBuilder setPadding(@Nullable Rect padding) {
if (padding != null) {
if (mPadding == null) {
mPadding = new Rect();
}
mPadding.set(padding);
} else {
mPadding = null;
}
return this;
}
@NonNull
public FastScrollerBuilder setTrackDrawable(@NonNull Drawable trackDrawable) {
mTrackDrawable = trackDrawable;
return this;
}
@NonNull
public FastScrollerBuilder setThumbDrawable(@NonNull Drawable thumbDrawable) {
mThumbDrawable = thumbDrawable;
return this;
}
@NonNull
public FastScrollerBuilder setPopupStyle(@NonNull Consumer<TextView> popupStyle) {
mPopupStyle = popupStyle;
return this;
}
@NonNull
public FastScrollerBuilder useDefaultStyle() {
Context context = mView.getContext();
mTrackDrawable = Utils.getGradientDrawableWithTintAttr(R.drawable.afs_track,
androidx.appcompat.R.attr.colorControlNormal, context);
mThumbDrawable = Utils.getGradientDrawableWithTintAttr(R.drawable.afs_thumb,
androidx.appcompat.R.attr.colorControlActivated, context);
mPopupStyle = PopupStyles.DEFAULT;
return this;
}
@NonNull
public FastScrollerBuilder useMd2Style() {
Context context = mView.getContext();
mTrackDrawable = Utils.getGradientDrawableWithTintAttr(R.drawable.afs_md2_track,
androidx.appcompat.R.attr.colorControlNormal, context);
mThumbDrawable = Utils.getGradientDrawableWithTintAttr(R.drawable.afs_md2_thumb,
androidx.appcompat.R.attr.colorControlActivated, context);
mPopupStyle = PopupStyles.MD2;
return this;
}
public void setAnimationHelper(@Nullable FastScroller.AnimationHelper animationHelper) {
mAnimationHelper = animationHelper;
}
public void disableScrollbarAutoHide() {
DefaultAnimationHelper animationHelper = new DefaultAnimationHelper(mView);
animationHelper.setScrollbarAutoHideEnabled(false);
mAnimationHelper = animationHelper;
}
@NonNull
public FastScroller build() {
return new FastScroller(mView, getOrCreateViewHelper(), mPadding, mTrackDrawable,
mThumbDrawable, mPopupStyle, getOrCreateAnimationHelper());
}
@NonNull
private FastScroller.ViewHelper getOrCreateViewHelper() {
if (mViewHelper != null) {
return mViewHelper;
}
if (mView instanceof ViewHelperProvider) {
return ((ViewHelperProvider) mView).getViewHelper();
} else if (mView instanceof RecyclerView) {
return new RecyclerViewHelper((RecyclerView) mView, mPopupTextProvider);
} else if (mView instanceof NestedScrollView) {
throw new UnsupportedOperationException("Please use "
+ FastScrollNestedScrollView.class.getSimpleName() + " instead of "
+ NestedScrollView.class.getSimpleName() + "for fast scroll");
} else if (mView instanceof ScrollView) {
throw new UnsupportedOperationException("Please use "
+ FastScrollScrollView.class.getSimpleName() + " instead of "
+ ScrollView.class.getSimpleName() + "for fast scroll");
} else if (mView instanceof WebView) {
throw new UnsupportedOperationException("Please use "
+ FastScrollWebView.class.getSimpleName() + " instead of "
+ WebView.class.getSimpleName() + "for fast scroll");
} else {
throw new UnsupportedOperationException(mView.getClass().getSimpleName()
+ " is not supported for fast scroll");
}
}
@NonNull
private FastScroller.AnimationHelper getOrCreateAnimationHelper() {
if (mAnimationHelper != null) {
return mAnimationHelper;
}
return new DefaultAnimationHelper(mView);
}
}

View file

@ -0,0 +1,133 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
public class FixItemDecorationRecyclerView extends RecyclerView {
public FixItemDecorationRecyclerView(@NonNull Context context) {
super(context);
}
public FixItemDecorationRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public FixItemDecorationRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void dispatchDraw(@NonNull Canvas canvas) {
for (int i = 0, count = getItemDecorationCount(); i < count; ++i) {
FixItemDecoration decor = (FixItemDecoration) super.getItemDecorationAt(i);
decor.getItemDecoration().onDraw(canvas, this, decor.getState());
}
super.dispatchDraw(canvas);
for (int i = 0, count = getItemDecorationCount(); i < count; ++i) {
FixItemDecoration decor = (FixItemDecoration) super.getItemDecorationAt(i);
decor.getItemDecoration().onDrawOver(canvas, this, decor.getState());
}
}
@Override
public void addItemDecoration(@NonNull ItemDecoration decor, int index) {
super.addItemDecoration(new FixItemDecoration(decor), index);
}
@NonNull
@Override
public ItemDecoration getItemDecorationAt(int index) {
return ((FixItemDecoration) super.getItemDecorationAt(index)).getItemDecoration();
}
@Override
public void removeItemDecoration(@NonNull ItemDecoration decor) {
if (!(decor instanceof FixItemDecoration)) {
for (int i = 0, count = getItemDecorationCount(); i < count; ++i) {
FixItemDecoration fixDecor = (FixItemDecoration) super.getItemDecorationAt(i);
if (fixDecor.getItemDecoration() == decor) {
decor = fixDecor;
break;
}
}
}
super.removeItemDecoration(decor);
}
private static class FixItemDecoration extends ItemDecoration {
@NonNull
private final ItemDecoration mItemDecoration;
private State mState;
private FixItemDecoration(@NonNull ItemDecoration itemDecoration) {
mItemDecoration = itemDecoration;
}
@NonNull
public ItemDecoration getItemDecoration() {
return mItemDecoration;
}
public State getState() {
return mState;
}
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
mState = state;
}
@Override
@SuppressWarnings("deprecation")
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent) {}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
@NonNull State state) {}
@Override
@SuppressWarnings("deprecation")
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent) {}
@Override
@SuppressWarnings("deprecation")
public void getItemOffsets(@NonNull Rect outRect, int itemPosition,
@NonNull RecyclerView parent) {
mItemDecoration.getItemOffsets(outRect, itemPosition, parent);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull State state) {
mItemDecoration.getItemOffsets(outRect, view, parent, state);
}
}
}

View file

@ -0,0 +1,143 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
public class FixOnItemTouchListenerRecyclerView extends RecyclerView {
@NonNull
private final OnItemTouchDispatcher mOnItemTouchDispatcher = new OnItemTouchDispatcher();
public FixOnItemTouchListenerRecyclerView(@NonNull Context context) {
super(context);
init();
}
public FixOnItemTouchListenerRecyclerView(@NonNull Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public FixOnItemTouchListenerRecyclerView(@NonNull Context context,
@Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
super.addOnItemTouchListener(mOnItemTouchDispatcher);
}
@Override
public void addOnItemTouchListener(@NonNull OnItemTouchListener listener) {
mOnItemTouchDispatcher.addListener(listener);
}
@Override
public void removeOnItemTouchListener(@NonNull OnItemTouchListener listener) {
mOnItemTouchDispatcher.removeListener(listener);
}
private static class OnItemTouchDispatcher implements OnItemTouchListener {
@NonNull
private final List<OnItemTouchListener> mListeners = new ArrayList<>();
@NonNull
private final Set<OnItemTouchListener> mTrackingListeners = new LinkedHashSet<>();
@Nullable
private OnItemTouchListener mInterceptingListener;
public void addListener(@NonNull OnItemTouchListener listener) {
mListeners.add(listener);
}
public void removeListener(@NonNull OnItemTouchListener listener) {
mListeners.remove(listener);
mTrackingListeners.remove(listener);
if (mInterceptingListener == listener) {
mInterceptingListener = null;
}
}
// @see RecyclerView#findInterceptingOnItemTouchListener
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
@NonNull MotionEvent event) {
int action = event.getAction();
for (OnItemTouchListener listener : mListeners) {
boolean intercepted = listener.onInterceptTouchEvent(recyclerView, event);
if (action == MotionEvent.ACTION_CANCEL) {
mTrackingListeners.remove(listener);
continue;
}
if (intercepted) {
mTrackingListeners.remove(listener);
event.setAction(MotionEvent.ACTION_CANCEL);
for (OnItemTouchListener trackingListener : mTrackingListeners) {
trackingListener.onInterceptTouchEvent(recyclerView, event);
}
event.setAction(action);
mTrackingListeners.clear();
mInterceptingListener = listener;
return true;
} else {
mTrackingListeners.add(listener);
}
}
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent event) {
if (mInterceptingListener == null) {
return;
}
mInterceptingListener.onTouchEvent(recyclerView, event);
int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mInterceptingListener = null;
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
for (OnItemTouchListener listener : mListeners) {
listener.onRequestDisallowInterceptTouchEvent(disallowIntercept);
}
}
}
}

View file

@ -0,0 +1,152 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.DrawableCompat;
class Md2PopupBackground extends Drawable {
@NonNull
private final Paint mPaint;
private final int mPaddingStart;
private final int mPaddingEnd;
@NonNull
private final Path mPath = new Path();
@NonNull
private final Matrix mTempMatrix = new Matrix();
public Md2PopupBackground(@NonNull Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Utils.getColorFromAttrRes(androidx.appcompat.R.attr.colorControlActivated, context));
mPaint.setStyle(Paint.Style.FILL);
Resources resources = context.getResources();
mPaddingStart = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_start);
mPaddingEnd = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_end);
}
@Override
public void draw(@NonNull Canvas canvas) {
canvas.drawPath(mPath, mPaint);
}
@Override
public boolean onLayoutDirectionChanged(int layoutDirection) {
updatePath();
return true;
}
@Override
public void setAlpha(int alpha) {}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {}
@Override
public boolean isAutoMirrored() {
return true;
}
private boolean needMirroring() {
return DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL;
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
protected void onBoundsChange(@NonNull Rect bounds) {
updatePath();
}
private void updatePath() {
mPath.reset();
Rect bounds = getBounds();
float width = bounds.width();
float height = bounds.height();
float r = height / 2;
float sqrt2 = (float) Math.sqrt(2);
// Ensure we are convex.
width = Math.max(r + sqrt2 * r, width);
pathArcTo(mPath, r, r, r, 90, 180);
float o1X = width - sqrt2 * r;
pathArcTo(mPath, o1X, r, r, -90, 45f);
float r2 = r / 5;
float o2X = width - sqrt2 * r2;
pathArcTo(mPath, o2X, r, r2, -45, 90);
pathArcTo(mPath, o1X, r, r, 45f, 45f);
mPath.close();
if (needMirroring()) {
mTempMatrix.setScale(-1, 1, width / 2, 0);
} else {
mTempMatrix.reset();
}
mTempMatrix.postTranslate(bounds.left, bounds.top);
mPath.transform(mTempMatrix);
}
private static void pathArcTo(@NonNull Path path, float centerX, float centerY, float radius,
float startAngle, float sweepAngle) {
path.arcTo(centerX - radius, centerY - radius, centerX + radius, centerY + radius,
startAngle, sweepAngle, false);
}
@Override
public boolean getPadding(@NonNull Rect padding) {
if (needMirroring()) {
padding.set(mPaddingEnd, 0, mPaddingStart, 0);
} else {
padding.set(mPaddingStart, 0, mPaddingEnd, 0);
}
return true;
}
@Override
public void getOutline(@NonNull Outline outline) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && !mPath.isConvex()) {
// The outline path must be convex before Q, but we may run into floating point error
// caused by calculation involving sqrt(2) or OEM implementation difference, so in this
// case we just omit the shadow instead of crashing.
super.getOutline(outline);
return;
}
outline.setConvexPath(mPath);
}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.core.util.Consumer;
public class PopupStyles {
private PopupStyles() {}
public static Consumer<TextView> DEFAULT = popupView -> {
Resources resources = popupView.getResources();
int minimumSize = resources.getDimensionPixelSize(R.dimen.afs_popup_min_size);
popupView.setMinimumWidth(minimumSize);
popupView.setMinimumHeight(minimumSize);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams)
popupView.getLayoutParams();
layoutParams.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
layoutParams.setMarginEnd(resources.getDimensionPixelOffset(R.dimen.afs_popup_margin_end));
popupView.setLayoutParams(layoutParams);
Context context = popupView.getContext();
popupView.setBackground(new AutoMirrorDrawable(Utils.getGradientDrawableWithTintAttr(
R.drawable.afs_popup_background, androidx.appcompat.R.attr.colorControlActivated, context)));
popupView.setEllipsize(TextUtils.TruncateAt.MIDDLE);
popupView.setGravity(Gravity.CENTER);
popupView.setIncludeFontPadding(false);
popupView.setSingleLine(true);
popupView.setTextColor(Utils.getColorFromAttrRes(android.R.attr.textColorPrimaryInverse,
context));
popupView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimensionPixelSize(
R.dimen.afs_popup_text_size));
};
public static Consumer<TextView> MD2 = popupView -> {
Resources resources = popupView.getResources();
popupView.setMinimumWidth(resources.getDimensionPixelSize(
R.dimen.afs_md2_popup_min_width));
popupView.setMinimumHeight(resources.getDimensionPixelSize(
R.dimen.afs_md2_popup_min_height));
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams)
popupView.getLayoutParams();
layoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
layoutParams.setMarginEnd(resources.getDimensionPixelOffset(
R.dimen.afs_md2_popup_margin_end));
popupView.setLayoutParams(layoutParams);
Context context = popupView.getContext();
popupView.setBackground(new Md2PopupBackground(context));
popupView.setElevation(resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_elevation));
popupView.setEllipsize(TextUtils.TruncateAt.MIDDLE);
popupView.setGravity(Gravity.CENTER);
popupView.setIncludeFontPadding(false);
popupView.setSingleLine(true);
popupView.setTextColor(Utils.getColorFromAttrRes(android.R.attr.textColorPrimaryInverse,
context));
popupView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimensionPixelSize(
R.dimen.afs_md2_popup_text_size));
};
}

View file

@ -0,0 +1,27 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.view.View;
import androidx.annotation.NonNull;
public interface PopupTextProvider {
@NonNull
CharSequence getPopupText(@NonNull View view, int position);
}

View file

@ -0,0 +1,23 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}

View file

@ -0,0 +1,224 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
class RecyclerViewHelper implements FastScroller.ViewHelper {
@NonNull
private final RecyclerView mView;
@Nullable
private final PopupTextProvider mPopupTextProvider;
@NonNull
private final Rect mTempRect = new Rect();
public RecyclerViewHelper(@NonNull RecyclerView view,
@Nullable PopupTextProvider popupTextProvider) {
mView = view;
mPopupTextProvider = popupTextProvider;
}
@Override
public void addOnPreDrawListener(@NonNull Runnable onPreDraw) {
mView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent,
@NonNull RecyclerView.State state) {
onPreDraw.run();
}
});
}
@Override
public void addOnScrollChangedListener(@NonNull Runnable onScrollChanged) {
mView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
onScrollChanged.run();
}
});
}
@Override
public void addOnTouchEventListener(@NonNull Predicate<MotionEvent> onTouchEvent) {
mView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
@NonNull MotionEvent event) {
return onTouchEvent.test(event);
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView,
@NonNull MotionEvent event) {
onTouchEvent.test(event);
}
});
}
@Override
public int getScrollRange() {
int itemCount = getItemCount();
if (itemCount == 0) {
return 0;
}
int itemHeight = getItemHeight();
if (itemHeight == 0) {
return 0;
}
return mView.getPaddingTop() + itemCount * itemHeight + mView.getPaddingBottom();
}
@Override
public int getScrollOffset() {
int firstItemPosition = getFirstItemPosition();
if (firstItemPosition == RecyclerView.NO_POSITION) {
return 0;
}
int itemHeight = getItemHeight();
int firstItemTop = getFirstItemOffset();
return mView.getPaddingTop() + firstItemPosition * itemHeight - firstItemTop;
}
@Override
public void scrollTo(int offset) {
// Stop any scroll in progress for RecyclerView.
mView.stopScroll();
offset -= mView.getPaddingTop();
int itemHeight = getItemHeight();
// firstItemPosition should be non-negative even if paddingTop is greater than item height.
int firstItemPosition = Math.max(0, offset / itemHeight);
int firstItemTop = firstItemPosition * itemHeight - offset;
scrollToPositionWithOffset(firstItemPosition, firstItemTop);
}
@Nullable
@Override
public CharSequence getPopupText() {
PopupTextProvider popupTextProvider = mPopupTextProvider;
if (popupTextProvider == null) {
RecyclerView.Adapter<?> adapter = mView.getAdapter();
if (adapter instanceof PopupTextProvider) {
popupTextProvider = (PopupTextProvider) adapter;
}
}
if (popupTextProvider == null) {
return null;
}
int position = getFirstItemAdapterPosition();
if (position == RecyclerView.NO_POSITION) {
return null;
}
return popupTextProvider.getPopupText(mView, position);
}
private int getItemCount() {
LinearLayoutManager linearLayoutManager = getVerticalLinearLayoutManager();
if (linearLayoutManager == null) {
return 0;
}
int itemCount = linearLayoutManager.getItemCount();
if (itemCount == 0) {
return 0;
}
if (linearLayoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) linearLayoutManager;
itemCount = (itemCount - 1) / gridLayoutManager.getSpanCount() + 1;
}
return itemCount;
}
private int getItemHeight() {
if (mView.getChildCount() == 0) {
return 0;
}
View itemView = mView.getChildAt(0);
mView.getDecoratedBoundsWithMargins(itemView, mTempRect);
return mTempRect.height();
}
private int getFirstItemPosition() {
int position = getFirstItemAdapterPosition();
LinearLayoutManager linearLayoutManager = getVerticalLinearLayoutManager();
if (linearLayoutManager == null) {
return RecyclerView.NO_POSITION;
}
if (linearLayoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) linearLayoutManager;
position /= gridLayoutManager.getSpanCount();
}
return position;
}
private int getFirstItemAdapterPosition() {
if (mView.getChildCount() == 0) {
return RecyclerView.NO_POSITION;
}
View itemView = mView.getChildAt(0);
LinearLayoutManager linearLayoutManager = getVerticalLinearLayoutManager();
if (linearLayoutManager == null) {
return RecyclerView.NO_POSITION;
}
return linearLayoutManager.getPosition(itemView);
}
private int getFirstItemOffset() {
if (mView.getChildCount() == 0) {
return RecyclerView.NO_POSITION;
}
View itemView = mView.getChildAt(0);
mView.getDecoratedBoundsWithMargins(itemView, mTempRect);
return mTempRect.top;
}
private void scrollToPositionWithOffset(int position, int offset) {
LinearLayoutManager linearLayoutManager = getVerticalLinearLayoutManager();
if (linearLayoutManager == null) {
return;
}
if (linearLayoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) linearLayoutManager;
position *= gridLayoutManager.getSpanCount();
}
// LinearLayoutManager actually takes offset from paddingTop instead of top of RecyclerView.
offset -= mView.getPaddingTop();
linearLayoutManager.scrollToPositionWithOffset(position, offset);
}
@Nullable
private LinearLayoutManager getVerticalLinearLayoutManager() {
RecyclerView.LayoutManager layoutManager = mView.getLayoutManager();
if (!(layoutManager instanceof LinearLayoutManager)) {
return null;
}
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
if (linearLayoutManager.getOrientation() != RecyclerView.VERTICAL) {
return null;
}
return linearLayoutManager;
}
}

View file

@ -0,0 +1,153 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.fastscroll;
import android.graphics.Canvas;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public abstract class SimpleViewHelper implements FastScroller.ViewHelper {
@Nullable
private Runnable mOnPreDrawListener;
@Nullable
private Runnable mOnScrollChangedListener;
@Nullable
private Predicate<MotionEvent> mOnTouchEventListener;
private boolean mListenerInterceptingTouchEvent;
@Override
public void addOnPreDrawListener(@Nullable Runnable listener) {
mOnPreDrawListener = listener;
}
public void draw(@NonNull Canvas canvas) {
if (mOnPreDrawListener != null) {
mOnPreDrawListener.run();
}
superDraw(canvas);
}
@Override
public void addOnScrollChangedListener(@Nullable Runnable listener) {
mOnScrollChangedListener = listener;
}
public void onScrollChanged(int left, int top, int oldLeft, int oldTop) {
superOnScrollChanged(left, top, oldLeft, oldTop);
if (mOnScrollChangedListener != null) {
mOnScrollChangedListener.run();
}
}
@Override
public void addOnTouchEventListener(@Nullable Predicate<MotionEvent> listener) {
mOnTouchEventListener = listener;
}
public boolean onInterceptTouchEvent(@NonNull MotionEvent event) {
if (mOnTouchEventListener != null && mOnTouchEventListener.test(event)) {
int actionMasked = event.getActionMasked();
if (actionMasked != MotionEvent.ACTION_UP
&& actionMasked != MotionEvent.ACTION_CANCEL) {
mListenerInterceptingTouchEvent = true;
}
if (actionMasked != MotionEvent.ACTION_CANCEL) {
MotionEvent cancelEvent = MotionEvent.obtain(event);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
superOnInterceptTouchEvent(cancelEvent);
cancelEvent.recycle();
} else {
superOnInterceptTouchEvent(event);
}
return true;
}
return superOnInterceptTouchEvent(event);
}
public boolean onTouchEvent(@NonNull MotionEvent event) {
if (mOnTouchEventListener != null) {
if (mListenerInterceptingTouchEvent) {
mOnTouchEventListener.test(event);
int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_CANCEL) {
mListenerInterceptingTouchEvent = false;
}
return true;
} else {
int actionMasked = event.getActionMasked();
if (actionMasked != MotionEvent.ACTION_DOWN && mOnTouchEventListener.test(event)) {
if (actionMasked != MotionEvent.ACTION_UP
&& actionMasked != MotionEvent.ACTION_CANCEL) {
mListenerInterceptingTouchEvent = true;
}
if (actionMasked != MotionEvent.ACTION_CANCEL) {
MotionEvent cancelEvent = MotionEvent.obtain(event);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
superOnTouchEvent(cancelEvent);
cancelEvent.recycle();
} else {
superOnTouchEvent(event);
}
return true;
}
}
}
return superOnTouchEvent(event);
}
@Override
public int getScrollRange() {
return computeVerticalScrollRange();
}
@Override
public int getScrollOffset() {
return computeVerticalScrollOffset();
}
@Override
public void scrollTo(int offset) {
scrollTo(getScrollX(), offset);
}
protected abstract void superDraw(@NonNull Canvas canvas);
protected abstract void superOnScrollChanged(int left, int top, int oldLeft, int oldTop);
protected abstract boolean superOnInterceptTouchEvent(@NonNull MotionEvent event);
protected abstract boolean superOnTouchEvent(@NonNull MotionEvent event);
protected abstract int computeVerticalScrollRange();
protected abstract int computeVerticalScrollOffset();
protected abstract int getScrollX();
protected abstract void scrollTo(int x, int y);
}

View file

@ -0,0 +1,72 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhanghai.android.fastscroll;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;
class Utils {
@ColorInt
public static int getColorFromAttrRes(@AttrRes int attrRes, @NonNull Context context) {
ColorStateList colorStateList = getColorStateListFromAttrRes(attrRes, context);
return colorStateList != null ? colorStateList.getDefaultColor() : 0;
}
@Nullable
public static ColorStateList getColorStateListFromAttrRes(@AttrRes int attrRes,
@NonNull Context context) {
TypedArray a = context.obtainStyledAttributes(new int[] { attrRes });
int resId;
try {
resId = a.getResourceId(0, 0);
if (resId != 0) {
return AppCompatResources.getColorStateList(context, resId);
}
return a.getColorStateList(0);
} finally {
a.recycle();
}
}
// Work around the bug that GradientDrawable didn't actually implement tinting until
// Lollipop MR1 (API 22).
@Nullable
public static Drawable getGradientDrawableWithTintAttr(@DrawableRes int drawableRes,
@AttrRes int tintAttrRes,
@NonNull Context context) {
Drawable drawable = AppCompatResources.getDrawable(context, drawableRes);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1
&& drawable instanceof GradientDrawable) {
drawable = DrawableCompat.wrap(drawable);
drawable.setTintList(getColorStateListFromAttrRes(tintAttrRes, context));
}
return drawable;
}
}

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.fastscroll;
import androidx.annotation.NonNull;
public interface ViewHelperProvider {
@NonNull
FastScroller.ViewHelper getViewHelper();
}

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!--
~ Any changes to the tint here needs to be mirrored in the call to
~ Utils.getGradientDrawableWithTintAttr() as well.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
android:tint="?colorControlActivated">
<corners android:radius="8dp" />
<padding
android:bottom="4dp"
android:top="4dp" />
<size
android:width="8dp"
android:height="52dp" />
<solid android:color="@android:color/white" />
</shape>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!--
~ Any changes to the tint here needs to be mirrored in the call to
~ Utils.getGradientDrawableWithTintAttr() as well.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
android:tint="?colorControlNormal">
<corners android:radius="8dp" />
<size android:width="8dp" />
<solid android:color="#39FFFFFF" />
</shape>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!--
~ Any changes to the tint here needs to be mirrored in the call to
~ Utils.getGradientDrawableWithTintAttr() as well.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
android:tint="?colorControlActivated">
<corners
android:bottomLeftRadius="44dp"
android:topLeftRadius="44dp"
android:topRightRadius="44dp" />
<padding
android:left="22dp"
android:right="22dp" />
<size
android:width="44dp"
android:height="44dp" />
<solid android:color="@android:color/white" />
</shape>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!--
~ Any changes to the tint here needs to be mirrored in the call to
~ Utils.getGradientDrawableWithTintAttr() as well.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
android:tint="?colorControlActivated">
<size
android:width="8dp"
android:height="48dp" />
<solid android:color="@android:color/white" />
</shape>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!--
~ It was later discovered that this XML drawable doesn't work on Lollipop (API 21) due to the bug
~ that GradientDrawable didn't actually implement tinting until Lollipop MR1 (API 22). So if you
~ need to support API 21, you'll need to work around it yourself.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:ignore="UnusedResources">
<item android:state_pressed="true">
<shape android:shape="rectangle" android:tint="?colorControlActivated">
<size android:width="8dp" android:height="48dp" />
<solid android:color="@android:color/white" />
</shape>
</item>
<item>
<shape android:shape="rectangle" android:tint="?colorControlNormal">
<size android:width="8dp" android:height="48dp" />
<solid android:color="@android:color/white" />
</shape>
</item>
</selector>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!--
~ Any changes to the tint here needs to be mirrored in the call to
~ Utils.getGradientDrawableWithTintAttr() as well.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
android:tint="?colorControlNormal">
<size android:width="8dp" />
<solid android:color="#39FFFFFF" />
</shape>

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<dimen name="afs_min_touch_target_size">48dp</dimen>
<dimen name="afs_popup_min_size">88dp</dimen>
<dimen name="afs_popup_margin_end">16dp</dimen>
<dimen name="afs_popup_text_size">45dp</dimen>
<dimen name="afs_md2_popup_min_width">78dp</dimen>
<dimen name="afs_md2_popup_min_height">64dp</dimen>
<dimen name="afs_md2_popup_margin_end">14dp</dimen>
<dimen name="afs_md2_popup_padding_start">16dp</dimen>
<dimen name="afs_md2_popup_padding_end">29dp</dimen>
<dimen name="afs_md2_popup_elevation">3dp</dimen>
<dimen name="afs_md2_popup_text_size">34dp</dimen>
</resources>

View file

@ -65,4 +65,5 @@ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "j
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-android" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
android-library = { id = "com.android.library", version.ref = "agp" }

View file

@ -15,3 +15,4 @@ dependencyResolutionManagement {
rootProject.name = "ToDo"
include(":app")
include(":fastscroll")