Convert FastScroller to kotlin

This commit is contained in:
FooIbar authored and tarsin committed 2023-06-22 10:01:41 +08:00
1 parent 457cf2e856
commit b59966e8a4
1 file changed
+236 -300
@@ -13,392 +13,328 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.legacy
package com.hippo.ehviewer.ui.legacy;
import android.animation.Animator
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.os.Handler
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
import com.hippo.ehviewer.R
import com.hippo.ehviewer.yorozuya.AnimationUtils
import com.hippo.ehviewer.yorozuya.LayoutUtils.dp2pix
import com.hippo.ehviewer.yorozuya.MathUtils.dist
import com.hippo.ehviewer.yorozuya.SimpleAnimatorListener
import com.hippo.ehviewer.yorozuya.SimpleHandler.Companion.instance
import kotlin.math.abs
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
class FastScroller : View {
private var mSimpleHandler: Handler? = null
private var mDraggable = false
private var mMinHandlerHeight = 0
private var mRecyclerView: RecyclerView? = null
private var mOnScrollChangeListener: RecyclerView.OnScrollListener? = null
private var mAdapter: RecyclerView.Adapter<*>? = null
private var mAdapterDataObserver: AdapterDataObserver? = null
private var mHandler: Drawable? = null
private var mHandlerOffset = INVALID
private var mHandlerHeight = INVALID
private var mDownX = INVALID.toFloat()
private var mDownY = INVALID.toFloat()
private var mLastMotionY = INVALID.toFloat()
private var mDragged = false
private var mCantDrag = false
private var mTouchSlop = 0
private var mListener: OnDragHandlerListener? = null
private var mShowAnimator: ObjectAnimator? = null
private var mHideAnimator: ObjectAnimator? = null
private val mHideRunnable = Runnable { mHideAnimator!!.start() }
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
constructor(context: Context) : super(context) {
init(context, null, 0)
}
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.yorozuya.AnimationUtils;
import com.hippo.ehviewer.yorozuya.LayoutUtils;
import com.hippo.ehviewer.yorozuya.MathUtils;
import com.hippo.ehviewer.yorozuya.SimpleAnimatorListener;
import com.hippo.ehviewer.yorozuya.SimpleHandler;
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs, 0)
}
public class FastScroller extends View {
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(context, attrs, defStyleAttr)
}
private static final int INVALID = -1;
private static final int SCROLL_BAR_FADE_DURATION = 500;
private static final int SCROLL_BAR_DELAY = 1000;
private static final int MIN_HANDLER_HEIGHT_DP = 32;
private Handler mSimpleHandler;
private boolean mDraggable;
private int mMinHandlerHeight;
private RecyclerView mRecyclerView;
private RecyclerView.OnScrollListener mOnScrollChangeListener;
private RecyclerView.Adapter<?> mAdapter;
private RecyclerView.AdapterDataObserver mAdapterDataObserver;
private Drawable mHandler;
private int mHandlerOffset = INVALID;
private int mHandlerHeight = INVALID;
private float mDownX = INVALID;
private float mDownY = INVALID;
private float mLastMotionY = INVALID;
private boolean mDragged = false;
private boolean mCantDrag = false;
private int mTouchSlop;
private OnDragHandlerListener mListener;
private ObjectAnimator mShowAnimator;
private ObjectAnimator mHideAnimator;
private final Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
mHideAnimator.start();
private fun init(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
mSimpleHandler = instance
val a = context.obtainStyledAttributes(attrs, R.styleable.FastScroller, defStyleAttr, 0)
mHandler = a.getDrawable(R.styleable.FastScroller_handler)
mDraggable = a.getBoolean(R.styleable.FastScroller_draggable, true)
a.recycle()
alpha = 0.0f
visibility = INVISIBLE
mMinHandlerHeight = dp2pix(context, MIN_HANDLER_HEIGHT_DP.toFloat())
mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
mShowAnimator = ObjectAnimator.ofFloat(this, "alpha", 1.0f).apply {
interpolator = AnimationUtils.FAST_SLOW_INTERPOLATOR
duration = SCROLL_BAR_FADE_DURATION.toLong()
}
};
public FastScroller(Context context) {
super(context);
init(context, null, 0);
}
public FastScroller(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public FastScroller(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
mSimpleHandler = SimpleHandler.getInstance();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FastScroller, defStyleAttr, 0);
mHandler = a.getDrawable(R.styleable.FastScroller_handler);
mDraggable = a.getBoolean(R.styleable.FastScroller_draggable, true);
a.recycle();
setAlpha(0.0f);
setVisibility(INVISIBLE);
mMinHandlerHeight = LayoutUtils.dp2pix(context, MIN_HANDLER_HEIGHT_DP);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mShowAnimator = ObjectAnimator.ofFloat(this, "alpha", 1.0f);
mShowAnimator.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
mShowAnimator.setDuration(SCROLL_BAR_FADE_DURATION);
mHideAnimator = ObjectAnimator.ofFloat(this, "alpha", 0.0f);
mHideAnimator.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR);
mHideAnimator.setDuration(SCROLL_BAR_FADE_DURATION);
mHideAnimator.addListener(new SimpleAnimatorListener() {
private boolean mCancel;
@Override
public void onAnimationCancel(Animator animation) {
mCancel = true;
}
@Override
public void onAnimationEnd(Animator animation) {
if (mCancel) {
mCancel = false;
} else {
setVisibility(INVISIBLE);
mHideAnimator = ObjectAnimator.ofFloat(this, "alpha", 0.0f).apply {
interpolator = AnimationUtils.SLOW_FAST_INTERPOLATOR
duration = SCROLL_BAR_FADE_DURATION.toLong()
addListener(object : SimpleAnimatorListener() {
private var mCancel = false
override fun onAnimationCancel(animation: Animator) {
mCancel = true
}
}
});
override fun onAnimationEnd(animation: Animator) {
if (mCancel) {
mCancel = false
} else {
visibility = INVISIBLE
}
}
})
}
}
public void setOnDragHandlerListener(OnDragHandlerListener listener) {
mListener = listener;
fun setOnDragHandlerListener(listener: OnDragHandlerListener?) {
mListener = listener
}
private void updatePosition(boolean show) {
private fun updatePosition(show: Boolean) {
if (mRecyclerView == null) {
return;
return
}
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
int height = getHeight() - paddingTop - paddingBottom;
int offset = mRecyclerView.computeVerticalScrollOffset();
int extent = mRecyclerView.computeVerticalScrollExtent();
int range = mRecyclerView.computeVerticalScrollRange();
val paddingTop = paddingTop
val paddingBottom = paddingBottom
val height = height - paddingTop - paddingBottom
val offset = mRecyclerView!!.computeVerticalScrollOffset()
val extent = mRecyclerView!!.computeVerticalScrollExtent()
val range = mRecyclerView!!.computeVerticalScrollRange()
if (height <= 0 || extent >= range || extent <= 0) {
return;
return
}
int endOffset = height * offset / range;
int endHeight = height * extent / range;
endHeight = Math.max(endHeight, mMinHandlerHeight);
endOffset = Math.min(endOffset, height - endHeight);
mHandlerOffset = endOffset + paddingTop;
mHandlerHeight = endHeight;
var endOffset = height * offset / range
var endHeight = height * extent / range
endHeight = endHeight.coerceAtLeast(mMinHandlerHeight)
endOffset = endOffset.coerceAtMost(height - endHeight)
mHandlerOffset = endOffset + paddingTop
mHandlerHeight = endHeight
if (show) {
if (mHideAnimator.isRunning()) {
mHideAnimator.cancel();
mShowAnimator.start();
} else if (getVisibility() != VISIBLE && !mShowAnimator.isRunning()) {
setVisibility(VISIBLE);
mShowAnimator.start();
if (mHideAnimator!!.isRunning) {
mHideAnimator!!.cancel()
mShowAnimator!!.start()
} else if (visibility != VISIBLE && !mShowAnimator!!.isRunning) {
visibility = VISIBLE
mShowAnimator!!.start()
}
Handler handler = mSimpleHandler;
handler.removeCallbacks(mHideRunnable);
val handler = mSimpleHandler
handler!!.removeCallbacks(mHideRunnable)
if (!mDragged) {
handler.postDelayed(mHideRunnable, SCROLL_BAR_DELAY);
handler.postDelayed(mHideRunnable, SCROLL_BAR_DELAY.toLong())
}
}
}
public void setHandlerDrawable(Drawable drawable) {
mHandler = drawable;
invalidate();
fun setHandlerDrawable(drawable: Drawable?) {
mHandler = drawable
invalidate()
}
public boolean isDraggable() {
return mDraggable;
}
public void setDraggable(boolean draggable) {
mDraggable = draggable;
if (mDragged) {
mDragged = false;
var isDraggable: Boolean
get() = mDraggable
set(draggable) {
mDraggable = draggable
if (mDragged) {
mDragged = false
}
mSimpleHandler!!.removeCallbacks(mHideRunnable)
mHideRunnable.run()
}
mSimpleHandler.removeCallbacks(mHideRunnable);
mHideRunnable.run();
}
val isAttached: Boolean
get() = mRecyclerView != null
public boolean isAttached() {
return mRecyclerView != null;
}
public void attachToRecyclerView(RecyclerView recyclerView) {
fun attachToRecyclerView(recyclerView: RecyclerView?) {
if (recyclerView == null) {
return;
return
}
if (mRecyclerView != null) {
throw new IllegalStateException("The FastScroller is already attached to a RecyclerView, " +
"call detachedFromRecyclerView first");
check(mRecyclerView == null) {
"The FastScroller is already attached to a RecyclerView, " +
"call detachedFromRecyclerView first"
}
mRecyclerView = recyclerView;
mOnScrollChangeListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
mRecyclerView = recyclerView
mOnScrollChangeListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
updatePosition(true)
invalidate()
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
updatePosition(true);
invalidate();
}
};
recyclerView.addOnScrollListener(mOnScrollChangeListener);
mAdapter = recyclerView.getAdapter();
}
recyclerView.addOnScrollListener(mOnScrollChangeListener!!)
mAdapter = recyclerView.adapter
if (mAdapter != null) {
mAdapterDataObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
super.onItemRangeChanged(positionStart, itemCount);
updatePosition(false);
invalidate();
mAdapterDataObserver = object : AdapterDataObserver() {
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
super.onItemRangeChanged(positionStart, itemCount)
updatePosition(false)
invalidate()
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
super.onItemRangeChanged(positionStart, itemCount, payload);
updatePosition(false);
invalidate();
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
super.onItemRangeChanged(positionStart, itemCount, payload)
updatePosition(false)
invalidate()
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
updatePosition(false);
invalidate();
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
updatePosition(false)
invalidate()
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
updatePosition(false);
invalidate();
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
super.onItemRangeRemoved(positionStart, itemCount)
updatePosition(false)
invalidate()
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
updatePosition(false);
invalidate();
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
updatePosition(false)
invalidate()
}
};
mAdapter.registerAdapterDataObserver(mAdapterDataObserver);
}
mAdapter!!.registerAdapterDataObserver(mAdapterDataObserver!!)
}
}
public void detachedFromRecyclerView() {
fun detachedFromRecyclerView() {
if (mRecyclerView != null && mOnScrollChangeListener != null) {
mRecyclerView.removeOnScrollListener(mOnScrollChangeListener);
mRecyclerView!!.removeOnScrollListener(mOnScrollChangeListener!!)
}
mRecyclerView = null;
mOnScrollChangeListener = null;
mRecyclerView = null
mOnScrollChangeListener = null
if (mAdapter != null && mAdapterDataObserver != null) {
mAdapter.unregisterAdapterDataObserver(mAdapterDataObserver);
mAdapter!!.unregisterAdapterDataObserver(mAdapterDataObserver!!)
}
mAdapter = null;
mAdapterDataObserver = null;
setAlpha(0.0f);
setVisibility(INVISIBLE);
mAdapter = null
mAdapterDataObserver = null
alpha = 0.0f
visibility = INVISIBLE
}
@Override
protected void onDraw(Canvas canvas) {
override fun onDraw(canvas: Canvas) {
if (mRecyclerView == null || mHandler == null) {
return;
return
}
if (mHandlerHeight == INVALID) {
updatePosition(false);
updatePosition(false)
}
if (mHandlerHeight == INVALID) {
return;
return
}
int paddingLeft = getPaddingLeft();
int saved = canvas.save();
canvas.translate(paddingLeft, mHandlerOffset);
mHandler.setBounds(0, 0, getWidth() - paddingLeft - getPaddingRight(), mHandlerHeight);
mHandler.draw(canvas);
canvas.restoreToCount(saved);
val paddingLeft = paddingLeft
val saved = canvas.save()
canvas.translate(paddingLeft.toFloat(), mHandlerOffset.toFloat())
mHandler!!.setBounds(0, 0, width - paddingLeft - paddingRight, mHandlerHeight)
mHandler!!.draw(canvas)
canvas.restoreToCount(saved)
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!mDraggable || getVisibility() != VISIBLE || mRecyclerView == null || mHandlerHeight == INVALID) {
return false;
override fun onTouchEvent(event: MotionEvent): Boolean {
if (!mDraggable || visibility != VISIBLE || mRecyclerView == null || mHandlerHeight == INVALID) {
return false
}
int action = event.getAction();
val action = event.action
if (action == MotionEvent.ACTION_DOWN) {
mCantDrag = false;
mCantDrag = false
}
if (mCantDrag) {
return false;
return false
}
switch (action) {
case MotionEvent.ACTION_DOWN: {
mDragged = false;
mDownX = event.getX();
mDownY = event.getY();
when (action) {
MotionEvent.ACTION_DOWN -> {
mDragged = false
mDownX = event.x
mDownY = event.y
if (mDownY < mHandlerOffset || mDownY > mHandlerOffset + mHandlerHeight) {
mCantDrag = true;
return false;
mCantDrag = true
return false
}
break;
}
case MotionEvent.ACTION_MOVE: {
MotionEvent.ACTION_MOVE -> {
if (!mDragged) {
float x = event.getX();
float y = event.getY();
val x = event.x
val y = event.y
// Check touch slop
if (MathUtils.dist(x, y, mDownX, mDownY) < mTouchSlop) {
return true;
if (dist(x, y, mDownX, mDownY) < mTouchSlop) {
return true
}
if (Math.abs(x - mDownX) > Math.abs(y - mDownY) ||
y < mHandlerOffset || y > mHandlerOffset + mHandlerHeight) {
mCantDrag = true;
return false;
if (abs(x - mDownX) > abs(y - mDownY) || y < mHandlerOffset || y > mHandlerOffset + mHandlerHeight) {
mCantDrag = true
return false
} else {
mDragged = true;
mSimpleHandler.removeCallbacks(mHideRunnable);
mDragged = true
mSimpleHandler!!.removeCallbacks(mHideRunnable)
// Update mLastMotionY
if (mDownY < mHandlerOffset || mDownY >= mHandlerOffset + mHandlerHeight) {
// the point out of handler, make the point in handler center
mLastMotionY = mHandlerOffset + mHandlerHeight / 2;
} else {
mLastMotionY = mDownY;
}
mLastMotionY =
if (mDownY < mHandlerOffset || mDownY >= mHandlerOffset + mHandlerHeight) {
// the point out of handler, make the point in handler center
(mHandlerOffset + mHandlerHeight / 2).toFloat()
} else {
mDownY
}
// Notify
if (mListener != null) {
mListener.onStartDragHandler();
mListener!!.onStartDragHandler()
}
}
}
int range = mRecyclerView.computeVerticalScrollRange();
val range = mRecyclerView!!.computeVerticalScrollRange()
if (range <= 0) {
break;
return true
}
float y = event.getY();
int scroll = (int) (range * (y - mLastMotionY) / (getHeight() - getPaddingTop() - getPaddingBottom()));
mRecyclerView.scrollBy(0, scroll);
mLastMotionY = y;
break;
val y = event.y
val scroll =
(range * (y - mLastMotionY) / (height - paddingTop - paddingBottom)).toInt()
mRecyclerView!!.scrollBy(0, scroll)
mLastMotionY = y
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
// Notify
if (mDragged && mListener != null) {
mListener.onEndDragHandler();
mListener!!.onEndDragHandler()
}
mDragged = false;
mSimpleHandler.postDelayed(mHideRunnable, SCROLL_BAR_DELAY);
break;
mDragged = false
mSimpleHandler!!.postDelayed(mHideRunnable, SCROLL_BAR_DELAY.toLong())
}
}
return true;
return true
}
public interface OnDragHandlerListener {
void onStartDragHandler();
interface OnDragHandlerListener {
fun onStartDragHandler()
fun onEndDragHandler()
}
void onEndDragHandler();
companion object {
private const val INVALID = -1
private const val SCROLL_BAR_FADE_DURATION = 500
private const val SCROLL_BAR_DELAY = 1000
private const val MIN_HANDLER_HEIGHT_DP = 32
}
}