修改交友布局
This commit is contained in:
@@ -0,0 +1,730 @@
|
||||
package com.xscm.moduleutil.widget.picker;
|
||||
|
||||
|
||||
import static lombok.Lombok.checkNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Camera;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.text.Layout;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.OverScroller;
|
||||
|
||||
import com.xscm.moduleutil.R;
|
||||
import com.xscm.moduleutil.utils.logger.Logger;
|
||||
import com.xscm.moduleutil.widget.Utils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class PickerView extends View {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
static final int DEFAULT_TEXT_SIZE_IN_SP = 14;
|
||||
static final int DEFAULT_ITEM_HEIGHT_IN_DP = 24;
|
||||
static final int DEFAULT_MAX_OFFSET_ITEM_COUNT = 3;
|
||||
private int preferredMaxOffsetItemCount = DEFAULT_MAX_OFFSET_ITEM_COUNT;
|
||||
private int selectedItemPosition;
|
||||
|
||||
private static final int DURATION_SHORT = 250;
|
||||
private static final int DURATION_LONG = 1000;
|
||||
|
||||
private Adapter<? extends PickerItem> adapter;
|
||||
|
||||
private Paint textPaint;
|
||||
private Rect textBounds = new Rect();
|
||||
|
||||
private GestureDetector gestureDetector;
|
||||
private OverScroller scroller;
|
||||
private boolean scrolling;
|
||||
private boolean pendingJustify;
|
||||
private boolean isScrollSuspendedByDownEvent;
|
||||
private float actionDownY;
|
||||
private float previousTouchedY;
|
||||
private int previousScrollerY;
|
||||
private int yOffset;
|
||||
private int minY;
|
||||
private int maxY;
|
||||
private int maxOverScrollY;
|
||||
private int touchSlop;
|
||||
|
||||
private int itemHeight;
|
||||
private int textSize;
|
||||
private int textColor = Color.BLACK;
|
||||
private int selectTextColor = Color.BLACK;
|
||||
private Typeface typeface;
|
||||
private boolean isCyclic;
|
||||
private boolean autoFitSize;
|
||||
private boolean curved;
|
||||
private Drawable selectedItemDrawable;
|
||||
private int selectedMaskPadding;
|
||||
private int[] DEFAULT_GRADIENT_COLORS = new int[]{0xcfffffff, 0x9fffffff, 0x5fffffff};
|
||||
private int[] gradientColors = DEFAULT_GRADIENT_COLORS;
|
||||
private GradientDrawable topMask;
|
||||
private GradientDrawable bottomMask;
|
||||
private Layout.Alignment textAlign = Layout.Alignment.ALIGN_CENTER;
|
||||
|
||||
private float radius;
|
||||
private Camera camera;
|
||||
private Matrix matrix;
|
||||
|
||||
public interface PickerItem {
|
||||
String getText();
|
||||
}
|
||||
|
||||
public PickerView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public PickerView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public PickerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs) {
|
||||
gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
int startScrollerY = yOffset - itemHeight * selectedItemPosition;
|
||||
|
||||
if (startScrollerY <= minY || startScrollerY >= maxY) {
|
||||
justify(DURATION_LONG);
|
||||
return true;
|
||||
}
|
||||
|
||||
scroller.fling(
|
||||
0, startScrollerY,
|
||||
0, (int) velocityY,
|
||||
0, 0,
|
||||
minY,
|
||||
maxY,
|
||||
0, maxOverScrollY);
|
||||
|
||||
if (DEBUG) {
|
||||
Logger.d("fling: " + startScrollerY + ", velocityY: " + velocityY);
|
||||
}
|
||||
|
||||
previousScrollerY = scroller.getCurrY();
|
||||
pendingJustify = true;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
scroller = new OverScroller(getContext());
|
||||
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
|
||||
if (isInEditMode()) {
|
||||
adapter = new Adapter<PickerItem>() {
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return getMaxCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PickerItem getItem(final int index) {
|
||||
return new PickerItem() {
|
||||
@Override
|
||||
public String getText() {
|
||||
return "Item " + index;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
} else {
|
||||
selectedMaskPadding = dp2px(18);
|
||||
selectedItemDrawable = Utils.getDrawable(getContext(), R.drawable.top_defaults_view_pickerview_selected_item);
|
||||
}
|
||||
|
||||
topMask = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, gradientColors);
|
||||
bottomMask = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, gradientColors);
|
||||
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PickerView);
|
||||
preferredMaxOffsetItemCount = typedArray.getInt(R.styleable.PickerView_preferredMaxOffsetItemCount, DEFAULT_MAX_OFFSET_ITEM_COUNT);
|
||||
if (preferredMaxOffsetItemCount <= 0)
|
||||
preferredMaxOffsetItemCount = DEFAULT_MAX_OFFSET_ITEM_COUNT;
|
||||
|
||||
int defaultItemHeight = Utils.pixelOfDp(getContext(), DEFAULT_ITEM_HEIGHT_IN_DP);
|
||||
itemHeight = typedArray.getDimensionPixelSize(R.styleable.PickerView_itemHeight, defaultItemHeight);
|
||||
if (itemHeight <= 0) itemHeight = defaultItemHeight;
|
||||
|
||||
int defaultTextSize = Utils.pixelOfScaled(getContext(), DEFAULT_TEXT_SIZE_IN_SP);
|
||||
textSize = typedArray.getDimensionPixelSize(R.styleable.PickerView_textSizec, defaultTextSize);
|
||||
textColor = typedArray.getColor(R.styleable.PickerView_textColorc, Color.BLACK);
|
||||
selectTextColor = typedArray.getColor(R.styleable.PickerView_selectTextColor, Color.BLACK);
|
||||
|
||||
isCyclic = typedArray.getBoolean(R.styleable.PickerView_isCyclic, false);
|
||||
autoFitSize = typedArray.getBoolean(R.styleable.PickerView_autoFitSize, true);
|
||||
curved = typedArray.getBoolean(R.styleable.PickerView_curved, false);
|
||||
typedArray.recycle();
|
||||
|
||||
initPaints();
|
||||
|
||||
camera = new Camera();
|
||||
matrix = new Matrix();
|
||||
}
|
||||
|
||||
protected int dp2px(float dp) {
|
||||
final float scale = getResources().getDisplayMetrics().density;
|
||||
return (int) (dp * scale + 0.5f);
|
||||
}
|
||||
|
||||
private void initPaints() {
|
||||
textPaint = new Paint();
|
||||
textPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
public Adapter getAdapter() {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
public <T extends PickerItem> void setAdapter(final Adapter<T> adapter) {
|
||||
checkNotNull(adapter, "adapter == null");
|
||||
if (adapter.getItemCount() > Integer.MAX_VALUE / itemHeight) {
|
||||
throw new RuntimeException("getItemCount() is too large, max count can be PickerView.getMaxCount()");
|
||||
}
|
||||
adapter.setPickerView(this);
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
public interface OnItemSelectedListener<T extends PickerItem> {
|
||||
void onItemSelected(T item);
|
||||
}
|
||||
|
||||
public <T extends PickerItem> void setItems(final List<T> items, final OnItemSelectedListener<T> listener) {
|
||||
final Adapter<T> simpleAdapter = new Adapter<T>() {
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getItem(int index) {
|
||||
return items.get(index);
|
||||
}
|
||||
};
|
||||
setAdapter(simpleAdapter);
|
||||
|
||||
setOnSelectedItemChangedListener(new OnSelectedItemChangedListener() {
|
||||
@Override
|
||||
public void onSelectedItemChanged(PickerView pickerView, int previousPosition, int selectedItemPosition) {
|
||||
if (listener != null) {
|
||||
listener.onItemSelected(simpleAdapter.getItem(selectedItemPosition));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected int getMaxCount() {
|
||||
return Integer.MAX_VALUE / itemHeight;
|
||||
}
|
||||
|
||||
public void notifyDataSetChanged() {
|
||||
if (adapter != null) {
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSelectedItem() {
|
||||
float centerPosition = centerPosition();
|
||||
int newSelectedItemPosition = (int) Math.floor(centerPosition);
|
||||
notifySelectedItemChangedIfNeeded(newSelectedItemPosition, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public abstract static class Adapter<T extends PickerItem> {
|
||||
private WeakReference<PickerView> pickerViewRef;
|
||||
|
||||
private void setPickerView(PickerView pickerView) {
|
||||
this.pickerViewRef = new WeakReference<>(pickerView);
|
||||
}
|
||||
|
||||
public void notifyDataSetChanged() {
|
||||
if (pickerViewRef != null) {
|
||||
PickerView pickerView = pickerViewRef.get();
|
||||
if (pickerView != null) {
|
||||
pickerView.updateSelectedItem();
|
||||
pickerView.computeScrollParams();
|
||||
if (!pickerView.scroller.isFinished()) {
|
||||
pickerView.scroller.forceFinished(true);
|
||||
}
|
||||
pickerView.justify(0);
|
||||
pickerView.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract int getItemCount();
|
||||
|
||||
public abstract T getItem(int index);
|
||||
|
||||
public T getLastItem() {
|
||||
return getItem(getItemCount() - 1);
|
||||
}
|
||||
|
||||
public String getText(int index) {
|
||||
if (getItem(index) == null) return "null";
|
||||
return getItem(index).getText();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPreferredMaxOffsetItemCount(int preferredMaxOffsetItemCount) {
|
||||
this.preferredMaxOffsetItemCount = preferredMaxOffsetItemCount;
|
||||
}
|
||||
|
||||
public void setItemHeight(int itemHeight) {
|
||||
if (this.itemHeight != itemHeight) {
|
||||
this.itemHeight = itemHeight;
|
||||
invalidate();
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextSize(int textSize) {
|
||||
if (this.textSize != textSize) {
|
||||
this.textSize = textSize;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextColor(int textColor) {
|
||||
if (this.textColor != textColor) {
|
||||
this.textColor = textColor;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTypeface(Typeface typeface) {
|
||||
if (this.typeface != typeface) {
|
||||
this.typeface = typeface;
|
||||
textPaint.setTypeface(typeface);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setCyclic(boolean cyclic) {
|
||||
if (this.isCyclic != cyclic) {
|
||||
isCyclic = cyclic;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoFitSize(boolean autoFitSize) {
|
||||
if (this.autoFitSize != autoFitSize) {
|
||||
this.autoFitSize = autoFitSize;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setCurved(boolean curved) {
|
||||
if (this.curved != curved) {
|
||||
this.curved = curved;
|
||||
invalidate();
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public int getSelectedItemPosition() {
|
||||
return clampItemPosition(selectedItemPosition);
|
||||
}
|
||||
|
||||
public void setSelectedItemPosition(int selectedItemPosition) {
|
||||
checkNotNull(adapter, "adapter must be set first");
|
||||
|
||||
notifySelectedItemChangedIfNeeded(selectedItemPosition);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public <T extends PickerItem> T getSelectedItem(Class<T> cls) {
|
||||
checkNotNull(adapter, "adapter must be set first");
|
||||
|
||||
PickerItem item = adapter.getItem(getSelectedItemPosition());
|
||||
if (!cls.isInstance(item)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return cls.cast(item);
|
||||
}
|
||||
|
||||
public interface OnSelectedItemChangedListener {
|
||||
void onSelectedItemChanged(PickerView pickerView, int previousPosition, int selectedItemPosition);
|
||||
}
|
||||
|
||||
private OnSelectedItemChangedListener onSelectedItemChangedListener;
|
||||
|
||||
public void setOnSelectedItemChangedListener(OnSelectedItemChangedListener onSelectedItemChangedListener) {
|
||||
this.onSelectedItemChangedListener = onSelectedItemChangedListener;
|
||||
}
|
||||
|
||||
private void notifySelectedItemChangedIfNeeded(int newSelectedItemPosition) {
|
||||
notifySelectedItemChangedIfNeeded(newSelectedItemPosition, false);
|
||||
}
|
||||
|
||||
private void notifySelectedItemChangedIfNeeded(int newSelectedItemPosition, boolean forceNotify) {
|
||||
int oldSelectedItemPosition = selectedItemPosition;
|
||||
int clampedNewSelectedItemPosition = clampItemPosition(newSelectedItemPosition);
|
||||
|
||||
boolean changed = forceNotify;
|
||||
if (isCyclic) {
|
||||
if (selectedItemPosition != newSelectedItemPosition) {
|
||||
selectedItemPosition = newSelectedItemPosition;
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
if (selectedItemPosition != clampedNewSelectedItemPosition) {
|
||||
selectedItemPosition = clampedNewSelectedItemPosition;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed && onSelectedItemChangedListener != null) {
|
||||
onSelectedItemChangedListener.onSelectedItemChanged(this, oldSelectedItemPosition, clampedNewSelectedItemPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
checkNotNull(adapter, "adapter == null");
|
||||
|
||||
int height = resolveSizeAndState(calculateIntrinsicHeight(), heightMeasureSpec, 0);
|
||||
computeScrollParams();
|
||||
setMeasuredDimension(widthMeasureSpec, height);
|
||||
}
|
||||
|
||||
private int calculateIntrinsicHeight() {
|
||||
if (curved) {
|
||||
radius = itemHeight / (float) Math.sin(Math.PI / (3 + 2 * preferredMaxOffsetItemCount));
|
||||
return (int) Math.ceil(2 * radius);
|
||||
} else {
|
||||
return (1 + 2 * preferredMaxOffsetItemCount) * itemHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
checkNotNull(adapter, "adapter == null");
|
||||
if (adapter.getItemCount() == 0 || itemHeight == 0) return;
|
||||
|
||||
if (!isInEditMode()) {
|
||||
selectedItemDrawable.setBounds(selectedMaskPadding, (getMeasuredHeight() - itemHeight) / 2, getMeasuredWidth() - selectedMaskPadding, (getMeasuredHeight() + itemHeight) / 2);
|
||||
selectedItemDrawable.draw(canvas);
|
||||
}
|
||||
|
||||
drawItems(canvas);
|
||||
drawMasks(canvas);
|
||||
}
|
||||
|
||||
private void drawItems(Canvas canvas) {
|
||||
// 绘制选中项
|
||||
float drawYOffset = yOffset + (getMeasuredHeight() - itemHeight) / 2;
|
||||
int itemPosition = selectedItemPosition;
|
||||
String text = adapter.getText(clampItemPosition(itemPosition));
|
||||
drawText(canvas, text, drawYOffset, 0);
|
||||
drawYOffset -= itemHeight;
|
||||
|
||||
// 绘制选中项上方的item
|
||||
itemPosition = selectedItemPosition - 1;
|
||||
while (drawYOffset + (itemHeight * (curved ? 2 : 1)) > 0) {
|
||||
if (isPositionInvalid(itemPosition) && !isCyclic) {
|
||||
break;
|
||||
}
|
||||
|
||||
text = adapter.getText(clampItemPosition(itemPosition));
|
||||
drawText(canvas, text, drawYOffset, -1);
|
||||
drawYOffset -= itemHeight;
|
||||
itemPosition--;
|
||||
}
|
||||
|
||||
// 绘制选中项下方的item
|
||||
drawYOffset = yOffset + (getMeasuredHeight() + itemHeight) / 2;
|
||||
itemPosition = selectedItemPosition + 1;
|
||||
while (drawYOffset - (itemHeight * (curved ? 1 : 0)) < getMeasuredHeight()) {
|
||||
if (isPositionInvalid(itemPosition) && !isCyclic) {
|
||||
break;
|
||||
}
|
||||
|
||||
text = adapter.getText(clampItemPosition(itemPosition));
|
||||
drawText(canvas, text, drawYOffset, 1);
|
||||
drawYOffset += itemHeight;
|
||||
itemPosition++;
|
||||
}
|
||||
}
|
||||
|
||||
private void drawMasks(Canvas canvas) {
|
||||
topMask.setBounds(0, 0, getMeasuredWidth(), (getMeasuredHeight() - itemHeight) / 2);
|
||||
topMask.draw(canvas);
|
||||
|
||||
bottomMask.setBounds(0, (getMeasuredHeight() + itemHeight) / 2, getMeasuredWidth(), getMeasuredHeight());
|
||||
bottomMask.draw(canvas);
|
||||
}
|
||||
|
||||
private void drawText(Canvas canvas, String text, float offset, int postion) {
|
||||
textPaint.setTextSize(textSize);
|
||||
if (postion == 0) {
|
||||
textPaint.setColor(selectTextColor);
|
||||
} else {
|
||||
textPaint.setColor(textColor);
|
||||
}
|
||||
textPaint.getTextBounds(text, 0, text.length(), textBounds);
|
||||
|
||||
if (autoFitSize) {
|
||||
while (getMeasuredWidth() < textBounds.width() && textPaint.getTextSize() > 16) {
|
||||
textPaint.setTextSize(textPaint.getTextSize() - 1);
|
||||
textPaint.getTextBounds(text, 0, text.length(), textBounds);
|
||||
}
|
||||
}
|
||||
|
||||
float textBottom = offset + (itemHeight + (textBounds.height())) / 2;
|
||||
|
||||
if (curved) {
|
||||
// 根据当前item的offset换算得到对应的倾斜角度,rotateRatio用于减小倾斜角度,否则倾斜角度过大会导致视觉效果不佳
|
||||
float rotateRatio = 2f / preferredMaxOffsetItemCount;
|
||||
double radian = Math.atan((radius - (offset + itemHeight / 2)) / radius) * rotateRatio;
|
||||
float degree = (float) (radian * 180 / Math.PI);
|
||||
camera.save();
|
||||
camera.rotateX(degree);
|
||||
camera.translate(0, 0, -Math.abs((radius / (2 + preferredMaxOffsetItemCount)) * (float) Math.sin(radian)));
|
||||
camera.getMatrix(matrix);
|
||||
matrix.preTranslate(-getMeasuredWidth() / 2, -getMeasuredHeight() / 2);
|
||||
matrix.postTranslate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
|
||||
canvas.save();
|
||||
canvas.concat(matrix);
|
||||
}
|
||||
if (textAlign == Layout.Alignment.ALIGN_CENTER) {
|
||||
textPaint.setTextAlign(Paint.Align.CENTER);
|
||||
canvas.drawText(text, getMeasuredWidth() / 2, textBottom, textPaint);
|
||||
} else if (textAlign == Layout.Alignment.ALIGN_OPPOSITE) {
|
||||
textPaint.setTextAlign(Paint.Align.RIGHT);
|
||||
canvas.drawText(text, getMeasuredWidth(), textBottom, textPaint);
|
||||
} else {
|
||||
textPaint.setTextAlign(Paint.Align.LEFT);
|
||||
canvas.drawText(text, 0, textBottom, textPaint);
|
||||
}
|
||||
if (curved) {
|
||||
canvas.restore();
|
||||
camera.restore();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performClick() {
|
||||
return super.performClick();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (gestureDetector.onTouchEvent(event)) {
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
float y = event.getY();
|
||||
int dy;
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
pendingJustify = false;
|
||||
actionDownY = y;
|
||||
previousTouchedY = y;
|
||||
scrolling = false;
|
||||
if (!scroller.isFinished()) {
|
||||
scroller.forceFinished(true);
|
||||
isScrollSuspendedByDownEvent = true;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (!scrolling && Math.abs(y - actionDownY) <= touchSlop) {
|
||||
break;
|
||||
}
|
||||
if (!scrolling) {
|
||||
scrolling = true;
|
||||
previousTouchedY = y;
|
||||
break;
|
||||
}
|
||||
pendingJustify = false;
|
||||
dy = (int) (y - previousTouchedY);
|
||||
handleOffset(dy);
|
||||
previousTouchedY = y;
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (!isScrollSuspendedByDownEvent && !scrolling && Math.abs(y - actionDownY) <= touchSlop) {
|
||||
// 单击事件
|
||||
performClick();
|
||||
previousScrollerY = yOffset - itemHeight * selectedItemPosition;
|
||||
// 最近的整数倍数的单元格高度
|
||||
int centerItemTopY = (getMeasuredHeight() - itemHeight) / 2;
|
||||
int centerItemBottomY = (getMeasuredHeight() + itemHeight) / 2;
|
||||
if (y >= centerItemTopY && y <= centerItemBottomY) break;
|
||||
|
||||
int scrollOffset;
|
||||
if (y < centerItemTopY) {
|
||||
scrollOffset = ((int) y - centerItemBottomY) / itemHeight * itemHeight;
|
||||
if (selectedItemPosition + scrollOffset / itemHeight < 0) break;
|
||||
} else {
|
||||
scrollOffset = ((int) y - centerItemTopY) / itemHeight * itemHeight;
|
||||
if (selectedItemPosition + scrollOffset / itemHeight > adapter.getItemCount() - 1)
|
||||
break;
|
||||
}
|
||||
scroller.startScroll(
|
||||
0, previousScrollerY,
|
||||
0, -scrollOffset,
|
||||
DURATION_SHORT);
|
||||
if (DEBUG) {
|
||||
Logger.d("scrollOffset = %d", scrollOffset);
|
||||
}
|
||||
break;
|
||||
}
|
||||
scrolling = false;
|
||||
isScrollSuspendedByDownEvent = false;
|
||||
|
||||
// align items
|
||||
dy = (int) (y - previousTouchedY);
|
||||
handleOffset(dy);
|
||||
justify(DURATION_SHORT);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeScroll() {
|
||||
if (scroller.computeScrollOffset()) {
|
||||
int scrollerY = scroller.getCurrY();
|
||||
if (DEBUG) {
|
||||
Logger.d("scrollerY = %d, previousScrollerY = %d", scrollerY, previousScrollerY);
|
||||
}
|
||||
int dy = scrollerY - previousScrollerY;
|
||||
handleOffset(dy);
|
||||
previousScrollerY = scrollerY;
|
||||
invalidate();
|
||||
} else {
|
||||
if (pendingJustify) {
|
||||
justify(DURATION_SHORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void computeScrollParams() {
|
||||
if (isCyclic) {
|
||||
minY = Integer.MIN_VALUE;
|
||||
maxY = Integer.MAX_VALUE;
|
||||
} else {
|
||||
minY = -(adapter.getItemCount() - 1) * itemHeight;
|
||||
maxY = 0;
|
||||
}
|
||||
maxOverScrollY = 2 * itemHeight;
|
||||
}
|
||||
|
||||
private boolean isPositionInvalid(int itemPosition) {
|
||||
return itemPosition < 0 || itemPosition >= adapter.getItemCount();
|
||||
}
|
||||
|
||||
private int clampItemPosition(int itemPosition) {
|
||||
if (adapter.getItemCount() == 0) return 0;
|
||||
|
||||
if (isCyclic) {
|
||||
if (itemPosition < 0) {
|
||||
itemPosition = itemPosition % adapter.getItemCount();
|
||||
if (itemPosition != 0) itemPosition += adapter.getItemCount();
|
||||
} else if (itemPosition >= adapter.getItemCount())
|
||||
itemPosition %= adapter.getItemCount();
|
||||
}
|
||||
|
||||
if (itemPosition < 0) itemPosition = 0;
|
||||
else if (itemPosition >= adapter.getItemCount()) itemPosition = adapter.getItemCount() - 1;
|
||||
return itemPosition;
|
||||
}
|
||||
|
||||
// 中心线切割的单元位置
|
||||
private float centerPosition() {
|
||||
return selectedItemPosition + 0.5f - yOffset / itemHeight;
|
||||
}
|
||||
|
||||
// 对齐item
|
||||
private void justify(int duration) {
|
||||
if (yOffset != 0) {
|
||||
int scrollOffset = -yOffset;
|
||||
if (selectedItemPosition != 0 && selectedItemPosition != adapter.getItemCount() - 1) {
|
||||
if (yOffset > 0) {
|
||||
if (yOffset > itemHeight / 3) {
|
||||
scrollOffset = itemHeight - yOffset;
|
||||
}
|
||||
} else {
|
||||
if (Math.abs(yOffset) > itemHeight / 3) {
|
||||
scrollOffset = -(itemHeight + yOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果item数量为1,总是回到0偏移
|
||||
if (adapter.getItemCount() > 1) {
|
||||
if (selectedItemPosition == 0 && yOffset < 0) {
|
||||
if (Math.abs(yOffset) > itemHeight / 3) {
|
||||
scrollOffset = -(itemHeight + yOffset);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedItemPosition == adapter.getItemCount() - 1 && yOffset > 0) {
|
||||
if (yOffset > itemHeight / 3) {
|
||||
scrollOffset = itemHeight - yOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousScrollerY = yOffset - itemHeight * selectedItemPosition;
|
||||
scroller.startScroll(
|
||||
0, previousScrollerY,
|
||||
0, scrollOffset,
|
||||
duration);
|
||||
if (DEBUG) {
|
||||
Logger.d("justify: duration = %d, yOffset = %d, scrollOffset = %d", duration, yOffset, scrollOffset);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
pendingJustify = false;
|
||||
}
|
||||
|
||||
private void handleOffset(int dy) {
|
||||
if (DEBUG) {
|
||||
Logger.d("yOffset = %d, dy = %d", yOffset, dy);
|
||||
}
|
||||
yOffset += dy;
|
||||
|
||||
if (Math.abs(yOffset) >= itemHeight) {
|
||||
// 滚动到边界时
|
||||
if (selectedItemPosition == 0 && dy >= 0 || selectedItemPosition == adapter.getItemCount() - 1 && dy <= 0) {
|
||||
if (Math.abs(yOffset) > maxOverScrollY) {
|
||||
yOffset = yOffset > 0 ? maxOverScrollY : -maxOverScrollY;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int preSelection = selectedItemPosition;
|
||||
notifySelectedItemChangedIfNeeded(selectedItemPosition - (yOffset / itemHeight));
|
||||
yOffset -= (preSelection - selectedItemPosition) * itemHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package com.xscm.moduleutil.widget.picker;
|
||||
|
||||
import static com.xscm.moduleutil.utils.UtilConfig.getContext;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
import com.luck.picture.lib.engine.CropFileEngine;
|
||||
import com.luck.picture.lib.style.BottomNavBarStyle;
|
||||
import com.luck.picture.lib.style.PictureSelectorStyle;
|
||||
import com.luck.picture.lib.style.SelectMainStyle;
|
||||
import com.luck.picture.lib.style.TitleBarStyle;
|
||||
import com.luck.picture.lib.utils.DensityUtil;
|
||||
import com.luck.picture.lib.utils.StyleUtils;
|
||||
import com.xscm.moduleutil.R;
|
||||
import com.yalantis.ucrop.UCrop;
|
||||
import com.yalantis.ucrop.UCropImageEngine;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @Author lxj$
|
||||
* @Time 2025-8-5 21:50:02$ $
|
||||
* @Description 图片选择器$
|
||||
*/
|
||||
public class PictureSelectorUtil {
|
||||
|
||||
public CropFileEngine ImageFileCropEngine;
|
||||
private PictureSelectorStyle selectorStyle;
|
||||
|
||||
public PictureSelectorStyle syltPictureSelector() {
|
||||
selectorStyle = new PictureSelectorStyle();
|
||||
// 主体风格
|
||||
SelectMainStyle numberSelectMainStyle = new SelectMainStyle();
|
||||
numberSelectMainStyle.setSelectNumberStyle(true);
|
||||
numberSelectMainStyle.setPreviewSelectNumberStyle(false);
|
||||
numberSelectMainStyle.setPreviewDisplaySelectGallery(true);
|
||||
numberSelectMainStyle.setSelectBackground(com.luck.picture.lib.R.drawable.ps_default_num_selector);
|
||||
numberSelectMainStyle.setPreviewSelectBackground(com.luck.picture.lib.R.drawable.ps_preview_checkbox_selector);
|
||||
numberSelectMainStyle.setSelectNormalBackgroundResources(com.luck.picture.lib.R.drawable.ps_select_complete_normal_bg);
|
||||
numberSelectMainStyle.setSelectNormalTextColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_53575e));
|
||||
numberSelectMainStyle.setSelectNormalText(com.luck.picture.lib.R.string.ps_send);
|
||||
numberSelectMainStyle.setAdapterPreviewGalleryBackgroundResource(com.luck.picture.lib.R.drawable.ps_preview_gallery_bg);
|
||||
numberSelectMainStyle.setAdapterPreviewGalleryItemSize(DensityUtil.dip2px(getContext(), 52));
|
||||
numberSelectMainStyle.setPreviewSelectText(com.luck.picture.lib.R.string.ps_select);
|
||||
numberSelectMainStyle.setPreviewSelectTextSize(14);
|
||||
numberSelectMainStyle.setPreviewSelectTextColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_white));
|
||||
numberSelectMainStyle.setPreviewSelectMarginRight(DensityUtil.dip2px(getContext(), 6));
|
||||
numberSelectMainStyle.setSelectBackgroundResources(com.luck.picture.lib.R.drawable.ps_select_complete_bg);
|
||||
numberSelectMainStyle.setSelectText(com.luck.picture.lib.R.string.ps_send_num);
|
||||
numberSelectMainStyle.setSelectTextColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_white));
|
||||
numberSelectMainStyle.setMainListBackgroundColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_black));
|
||||
numberSelectMainStyle.setCompleteSelectRelativeTop(true);
|
||||
numberSelectMainStyle.setPreviewSelectRelativeBottom(true);
|
||||
numberSelectMainStyle.setAdapterItemIncludeEdge(false);
|
||||
|
||||
// 头部TitleBar 风格
|
||||
TitleBarStyle numberTitleBarStyle = new TitleBarStyle();
|
||||
numberTitleBarStyle.setHideCancelButton(true);
|
||||
numberTitleBarStyle.setAlbumTitleRelativeLeft(true);
|
||||
numberTitleBarStyle.setTitleAlbumBackgroundResource(com.luck.picture.lib.R.drawable.ps_album_bg);
|
||||
numberTitleBarStyle.setTitleDrawableRightResource(com.luck.picture.lib.R.drawable.ps_ic_grey_arrow);
|
||||
numberTitleBarStyle.setPreviewTitleLeftBackResource(com.luck.picture.lib.R.drawable.ps_ic_normal_back);
|
||||
|
||||
// 底部NavBar 风格
|
||||
BottomNavBarStyle numberBottomNavBarStyle = new BottomNavBarStyle();
|
||||
numberBottomNavBarStyle.setBottomPreviewNarBarBackgroundColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_half_grey));
|
||||
numberBottomNavBarStyle.setBottomPreviewNormalText(com.luck.picture.lib.R.string.ps_preview);
|
||||
numberBottomNavBarStyle.setBottomPreviewNormalTextColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_9b));
|
||||
numberBottomNavBarStyle.setBottomPreviewNormalTextSize(16);
|
||||
numberBottomNavBarStyle.setCompleteCountTips(false);
|
||||
numberBottomNavBarStyle.setBottomPreviewSelectText(com.luck.picture.lib.R.string.ps_preview_num);
|
||||
numberBottomNavBarStyle.setBottomPreviewSelectTextColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_white));
|
||||
|
||||
|
||||
selectorStyle.setTitleBarStyle(numberTitleBarStyle);
|
||||
selectorStyle.setBottomBarStyle(numberBottomNavBarStyle);
|
||||
selectorStyle.setSelectMainStyle(numberSelectMainStyle);
|
||||
return selectorStyle;
|
||||
}
|
||||
|
||||
public class ImageFileCropEngine implements CropFileEngine {
|
||||
|
||||
@Override
|
||||
public void onStartCrop(Fragment fragment, Uri srcUri, Uri destinationUri, ArrayList<String> dataSource, int requestCode) {
|
||||
UCrop.Options options = buildOptions();
|
||||
UCrop uCrop = UCrop.of(srcUri, destinationUri, dataSource);
|
||||
uCrop.withOptions(options);
|
||||
uCrop.setImageEngine(new UCropImageEngine() {
|
||||
@Override
|
||||
public void loadImage(Context context, String url, ImageView imageView) {
|
||||
// if (!ImageLoaderUtils.assertValidRequest(context)) {
|
||||
// return;
|
||||
// }
|
||||
Glide.with(context).load(url).override(180, 180).into(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadImage(Context context, Uri url, int maxWidth, int maxHeight, OnCallbackListener<Bitmap> call) {
|
||||
Glide.with(context).asBitmap().load(url).override(maxWidth, maxHeight).into(new CustomTarget<Bitmap>() {
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
|
||||
if (call != null) {
|
||||
call.onCall(resource);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCleared(@Nullable Drawable placeholder) {
|
||||
if (call != null) {
|
||||
call.onCall(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
uCrop.start(fragment.requireActivity(), fragment, requestCode);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 配制UCrop,可根据需求自我扩展
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public UCrop.Options buildOptions() {
|
||||
UCrop.Options options = new UCrop.Options();
|
||||
options.setHideBottomControls(true);//是否显示裁剪
|
||||
options.setFreeStyleCropEnabled(true);//裁剪是否可以拖动
|
||||
options.setShowCropFrame(true);//是否显示裁剪边缘
|
||||
options.setShowCropGrid(true);//是否显示裁剪网格
|
||||
options.setCircleDimmedLayer(false);//圆形头像裁剪
|
||||
options.withAspectRatio(1, 1);
|
||||
options.isCropDragSmoothToCenter(false);
|
||||
options.isForbidCropGifWebp(false);
|
||||
options.isForbidSkipMultipleCrop(true);
|
||||
options.setMaxScaleMultiplier(100);
|
||||
if (selectorStyle != null && selectorStyle.getSelectMainStyle().getStatusBarColor() != 0) {
|
||||
SelectMainStyle mainStyle = selectorStyle.getSelectMainStyle();
|
||||
boolean isDarkStatusBarBlack = mainStyle.isDarkStatusBarBlack();
|
||||
int statusBarColor = mainStyle.getStatusBarColor();
|
||||
options.isDarkStatusBarBlack(isDarkStatusBarBlack);
|
||||
if (StyleUtils.checkStyleValidity(statusBarColor)) {
|
||||
options.setStatusBarColor(statusBarColor);
|
||||
options.setToolbarColor(statusBarColor);
|
||||
} else {
|
||||
options.setStatusBarColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_grey));
|
||||
options.setToolbarColor(ContextCompat.getColor(getContext(),com.luck.picture.lib.R.color.ps_color_grey));
|
||||
}
|
||||
TitleBarStyle titleBarStyle = selectorStyle.getTitleBarStyle();
|
||||
if (StyleUtils.checkStyleValidity(titleBarStyle.getTitleTextColor())) {
|
||||
options.setToolbarWidgetColor(titleBarStyle.getTitleTextColor());
|
||||
} else {
|
||||
options.setToolbarWidgetColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_white));
|
||||
}
|
||||
} else {
|
||||
options.setStatusBarColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_grey));
|
||||
options.setToolbarColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_grey));
|
||||
options.setToolbarWidgetColor(ContextCompat.getColor(getContext(),com.luck.picture.lib. R.color.ps_color_white));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user