升级 Java 版本
将 zhanghai/AndroidFastScroll 添加为子模块以确保 Java 版本的升级(修复资源引用错误,未做其它改动)
This commit is contained in:
parent
eb2adf5479
commit
d3249dd8a7
34 changed files with 2641 additions and 9 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
1
fastscroll/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
42
fastscroll/build.gradle.kts
Normal file
42
fastscroll/build.gradle.kts
Normal 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)
|
||||
}
|
||||
0
fastscroll/consumer-rules.pro
Normal file
0
fastscroll/consumer-rules.pro
Normal file
21
fastscroll/proguard-rules.pro
vendored
Normal file
21
fastscroll/proguard-rules.pro
vendored
Normal 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
|
||||
19
fastscroll/src/main/AndroidManifest.xml
Normal file
19
fastscroll/src/main/AndroidManifest.xml
Normal 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 />
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
};
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
38
fastscroll/src/main/res/drawable/afs_md2_thumb.xml
Normal file
38
fastscroll/src/main/res/drawable/afs_md2_thumb.xml
Normal 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>
|
||||
32
fastscroll/src/main/res/drawable/afs_md2_track.xml
Normal file
32
fastscroll/src/main/res/drawable/afs_md2_track.xml
Normal 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>
|
||||
41
fastscroll/src/main/res/drawable/afs_popup_background.xml
Normal file
41
fastscroll/src/main/res/drawable/afs_popup_background.xml
Normal 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>
|
||||
32
fastscroll/src/main/res/drawable/afs_thumb.xml
Normal file
32
fastscroll/src/main/res/drawable/afs_thumb.xml
Normal 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>
|
||||
39
fastscroll/src/main/res/drawable/afs_thumb_stateful.xml
Normal file
39
fastscroll/src/main/res/drawable/afs_thumb_stateful.xml
Normal 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>
|
||||
30
fastscroll/src/main/res/drawable/afs_track.xml
Normal file
30
fastscroll/src/main/res/drawable/afs_track.xml
Normal 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>
|
||||
34
fastscroll/src/main/res/values/dimens.xml
Normal file
34
fastscroll/src/main/res/values/dimens.xml
Normal 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>
|
||||
|
|
@ -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" }
|
||||
|
|
@ -15,3 +15,4 @@ dependencyResolutionManagement {
|
|||
|
||||
rootProject.name = "ToDo"
|
||||
include(":app")
|
||||
include(":fastscroll")
|
||||
|
|
|
|||
Loading…
Reference in a new issue