注:此Demo只是为了学习自定义相关知识,不建议在项目中引用使用.
已实现功能:
1.可设置每行显示的列数、删除图标、加号图片、设置padding,删除图标与图片的间距,
行和列的间距以及删除图标的大小;
2.match_parent模式下自动计算宽高,wrap_content模式下可设置图片的最小宽高,超过了
屏幕的宽度,则按match_parent计算
3.超过满屏可滑动查看,可设置是否拖拽和是否可以滑动,以及设置显示图片的数量
待实现和优化修改:
1.拖拽时屏幕自动滚动;
2.开启可滑动时,超出满屏时,拖拽view错乱问题;
3.拖拽以及拖拽动画优化;
4.支持margin
在不超过满屏的情况下,滑动和拖拽可同时开启,超过了满屏同时开启的话拖拽会出现问题(待修改问题)。
先看一下效果:
xml布局:
xmlns:app="http://schemas.android.com/apk/res-auto"
<com.example.administrator.myapplication.view.PhotosDisplayView
android:id="@+id/photosDisplayView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:padding="10dp"
app:defaultAddImage="@mipmap/add_image"
app:defaultDeleteImage="@mipmap/delete_image"
app:dividerHeWidth="10dp"
app:dividerVeWidth="10dp"
app:isCanScroller="true"
app:isOpenAnimation="true"
app:linePhotoNum="4"
app:maxPhotoNum="8">
Activity代码:
public class Main3Activity extends AppCompatActivity implements PhotosDisplayView.OnItemClickListener {
@BindView(id = R.id.photosDisplayView)
private PhotosDisplayView photosDisplayView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
BindUtils.initBindView(this);
photosDisplayView.setOnItemClickListener(this);
photosDisplayView.post(new Runnable() {
@Override
public void run() {
Rect outRect2 = new Rect();
getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(outRect2);
int height = outRect2.height();
photosDisplayView.getPMHeight(height);
}
});
}
private int index;
@Override
public void addImageClick() {
index++;
photosDisplayView.addShowImage(System.currentTimeMillis() + "", index);
}
@Override
public void lookImageClick(String imageUrl) {
}
}
PhotosDisplayView 代码:
import android.animation.LayoutTransition;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.DragEvent;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Scroller;
import com.example.administrator.myapplication.R;
import java.util.ArrayList;
import java.util.List;
public class PhotosDisplayView extends ViewGroup {
//默认一列4个
private int defaultLinePhotoNum;
//最大可添加图片数量
private int defaultMaxPhotoNum;
//删除图片与图片之间的间距大小
private int defaultSmallImgSpace;
private int defaultSmallImgSize;
//是否打开删除动画
private boolean isOpenAnimation = true;
//是否可拖拽
private boolean isCanDrag = true;
private boolean isCanScroller = true;//是否可以滑动
private View mDragView;//拖拽的view
private List<String> imageList = new ArrayList<>();
//如果父控件不是match_parent,则使用默认宽高(正方形),
private int defaultImageWidth;
//默认间距
private int defaultHeDividerWidth;//横向
private int defaultVeDividerWidth;//纵向
private int deleteImageId;//默认删除的图片
private int addImageId;//默认添加的图片
private int mScreenWidth; //屏幕宽度
private VelocityTracker mVelocityTracker;
private int mMinimumVelocity;//最小速率
private int mMaximumVelocity;//最大速率
private int mScaledTouchSlop; //最小距离
private boolean mIsBeingDragged = false;//是否在拖动状态
private int display;//view绘制区域的高度
private int paddingLeft;
private int paddingRight; //padding
private int paddingTop;
private int paddingBottom;
private int mLastMotionY;
private Scroller mScroller;
private Context mContext;
private OnItemClickListener onItemClickListener;
public PhotosDisplayView(Context context) {
super(context, null);
}
public PhotosDisplayView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
mContext = context;
mScreenWidth = getScreenSize(mContext).widthPixels;
defaultImageWidth = dip2px(context, 120);
defaultSmallImgSize = dip2px(context, 15);
defaultSmallImgSpace = dip2px(context, 8);
mScroller = new Scroller(context);
ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
mMinimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
mMaximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
mScaledTouchSlop = viewConfiguration.getScaledTouchSlop();
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PhotosDisplayView);
defaultLinePhotoNum = typedArray.getInt(R.styleable.PhotosDisplayView_linePhotoNum, defaultLinePhotoNum);
defaultSmallImgSpace = (int) typedArray.getDimension(R.styleable.PhotosDisplayView_defaultSmallImgSpace, defaultSmallImgSpace);
defaultMaxPhotoNum = typedArray.getInt(R.styleable.PhotosDisplayView_maxPhotoNum, defaultMaxPhotoNum);
isCanDrag = typedArray.getBoolean(R.styleable.PhotosDisplayView_isCanDrag, isCanDrag);
isOpenAnimation = typedArray.getBoolean(R.styleable.PhotosDisplayView_isOpenAnimation, isOpenAnimation);
isCanScroller = typedArray.getBoolean(R.styleable.PhotosDisplayView_isCanScroller, isCanScroller);
defaultImageWidth = (int) typedArray.getDimension(R.styleable.PhotosDisplayView_minImageWidth, defaultImageWidth);
defaultSmallImgSize = (int) typedArray.getDimension(R.styleable.PhotosDisplayView_defaultSmallImgSize, defaultSmallImgSize);
defaultHeDividerWidth = (int) typedArray.getDimension(R.styleable.PhotosDisplayView_dividerHeWidth, defaultHeDividerWidth);
defaultVeDividerWidth = (int) typedArray.getDimension(R.styleable.PhotosDisplayView_dividerVeWidth, defaultVeDividerWidth);
addImageId = typedArray.getResourceId(R.styleable.PhotosDisplayView_defaultAddImage, R.mipmap.add_image);
deleteImageId = typedArray.getResourceId(R.styleable.PhotosDisplayView_defaultDeleteImage, R.mipmap.delete_image);
typedArray.recycle();
if (isOpenAnimation) {
//列表动画
LayoutTransition transition = new LayoutTransition();
transition.setDuration(300);
setLayoutTransition(transition);
setOnDragListener(mOnDragListener);
}
showAddImage();
}
public PhotosDisplayView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private int[] s = new int[]{R.mipmap.one, R.mipmap.two, R.mipmap.three};
/**
* 默认添加加号图片
*
* @param imageUrl
* @param index
*/
private void addImage(String imageUrl, int index) {
final FrameLayout frameLayout = new FrameLayout(mContext);
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(s[index % 3]);
imageView.setAdjustViewBounds(true);
FrameLayout.LayoutParams layoutParams1 = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
layoutParams1.setMargins(defaultSmallImgSpace, defaultSmallImgSpace, defaultSmallImgSpace, defaultSmallImgSpace);
imageView.setLayoutParams(layoutParams1);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
frameLayout.addView(imageView);
ImageView deleteView = new ImageView(mContext);
deleteView.setImageResource(deleteImageId);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(defaultSmallImgSize, defaultSmallImgSize);
layoutParams.gravity = Gravity.RIGHT | Gravity.TOP;
deleteView.setLayoutParams(layoutParams);
deleteView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
removeView(frameLayout);
if (!isHaveDeleteImage()) {
showAddImage();
}
}
});
frameLayout.addView(deleteView);
frameLayout.setTag(imageUrl);
addView(frameLayout, 0);
}
/**
* 添加图片
*/
private void showAddImage() {
FrameLayout frameLayout = new FrameLayout(mContext);
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(addImageId);
imageView.setAdjustViewBounds(true);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
imageView.setLayoutParams(layoutParams);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
frameLayout.addView(imageView);
frameLayout.setTag("");
addView(frameLayout);
}
/**
* 是否包含加号图片
*
* @return
*/
private boolean isHaveDeleteImage() {
boolean have = false;
for (int i = 0; i < getChildCount(); i++) {
View childAt = getChildAt(i);
if (childAt.getTag().equals("")) {
have = true;
break;
}
}
return have;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
paddingLeft = getPaddingLeft();
paddingTop = getPaddingTop();
paddingRight = getPaddingRight();
paddingBottom = getPaddingBottom();
int totalWidth;
//宽高初始值为加号图片的占用空间
int totalHeight = defaultImageWidth;
int childCount = getChildCount();
//是否是match_parent
boolean isHeightExactly = false;
if (widthMode == MeasureSpec.EXACTLY) {
totalWidth = widthSize;
getSelfWidth(totalWidth);
} else {
//图片高度 * 行数 + 上下padding + 多行图片之间间隔的总间距
totalWidth = defaultImageWidth * defaultLinePhotoNum + (defaultLinePhotoNum - 1) * defaultVeDividerWidth + paddingLeft + paddingRight;
if (totalWidth > mScreenWidth) {//占用宽度超过了屏幕宽度,就设置为屏幕宽度,则不使用自定义的defaultImageWidth,按match_parent来算
totalWidth = mScreenWidth;
getSelfWidth(totalWidth);
}
}
if (heightMode == MeasureSpec.EXACTLY) {
totalHeight = heightSize;
isHeightExactly = true;
}
for (int i = 0; i < childCount; i++) {
View childAt = getChildAt(i);
childAt.setLayoutParams(new ViewGroup.MarginLayoutParams(defaultImageWidth, defaultImageWidth));
measureChild(childAt, widthMeasureSpec, heightMeasureSpec);
//排除match_parent, 累加高度
if (!isHeightExactly && i % defaultLinePhotoNum == 0 && i != 0) {
totalHeight = totalHeight + defaultHeDividerWidth + defaultImageWidth;
}
}
totalHeight += paddingTop + paddingBottom;
setMeasuredDimension(totalWidth, totalHeight);
}
public void getSelfWidth(int totalWidth) {
//(总宽-左右padding-多行图片之间间隔的总间距) / 列数
defaultImageWidth = (totalWidth - paddingLeft - paddingRight - (defaultLinePhotoNum - 1) * defaultVeDividerWidth) / defaultLinePhotoNum;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int left = paddingLeft, top = paddingTop, right;
for (int i = 0; i < childCount; i++) {
final View childAt = getChildAt(i);
if (i % defaultLinePhotoNum == 0) {
if (i != 0)
top = top + childAt.getMeasuredHeight() + defaultHeDividerWidth;
left = paddingLeft;
} else {
left = left + childAt.getMeasuredWidth() + defaultVeDividerWidth;
}
right = left + childAt.getMeasuredWidth();
childAt.layout(left, top, right, top + childAt.getMeasuredHeight());
childAt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (null == onItemClickListener) {
return;
}
if (v.getTag().equals("")) {
onItemClickListener.addImageClick();
} else {
onItemClickListener.lookImageClick(v.getTag().toString());
}
}
});
childAt.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (!isCanDrag || v.getTag().equals("")) {
return true;
}
v.startDrag(null, new DragShadowBuilder(v), null, 0);
mDragView = v;
return true;
}
});
}
}
/**
* 拖拽监听
*/
private OnDragListener mOnDragListener = new OnDragListener() {
private Rect[] mRect;
//记录每个子view的位置
private void initRect() {
int childCount = getChildCount();
mRect = new Rect[childCount];
for (int i = 0; i < childCount; i++) {
View childAt = getChildAt(i);
mRect[i] = new Rect(childAt.getLeft(), childAt.getTop(), childAt.getRight(), childAt.getBottom());
}
}
//已经拖动到了其他view的区域
private int getChildIndex(int x, int y) {
for (int i = 0; i < mRect.length; i++) {
if (mRect[i].contains(x, y)) {
return i;
}
}
return -1;
}
@Override
public boolean onDrag(View v, DragEvent event) {
if (!isCanDrag) {
return true;
}
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
initRect();
break;
case DragEvent.ACTION_DRAG_LOCATION:
int childIndex = getChildIndex((int) event.getX(), (int) event.getY());
if (childIndex >= 0 && getChildAt(childIndex) != mDragView && !getChildAt(childIndex).getTag().equals("")) {
removeView(mDragView);
addView(mDragView, childIndex);
}
break;
case DragEvent.ACTION_DRAG_ENDED:
/*ScaleAnimation animation1 = new ScaleAnimation(
1.5f, 1.0f, 1.5f, 1.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f
);
animation1.setDuration(300);
animation1.setFillAfter(true);
mDragingView.startAnimation(animation1);*/
break;
}
return true;
}
};
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (isCanScroller) {
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && mIsBeingDragged) {
return true;
}
if (super.onInterceptTouchEvent(ev)) {
return true;
}
switch (action) {
case MotionEvent.ACTION_DOWN: {
mLastMotionY = (int) ev.getY();
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
mIsBeingDragged = !mScroller.isFinished();
break;
}
case MotionEvent.ACTION_MOVE: {
final int y = (int) ev.getY();
final int yDiff = Math.abs(y - mLastMotionY);
//根据最小距离,判断是滑动,还是点击
if (yDiff > mScaledTouchSlop) {
mIsBeingDragged = true;
mLastMotionY = y;
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
}
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mIsBeingDragged = false;
recycleVelocityTracker();
break;
}
return mIsBeingDragged;
} else
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isCanScroller) {
initVelocityTrackerIfNotExists();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
if ((mIsBeingDragged = !mScroller.isFinished())) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
// mLastMotionY = (int) event.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
final int y = (int) event.getY();
int deltaY = mLastMotionY - y;
/* if (!mIsBeingDragged && Math.abs(deltaY) > mScaledTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
mIsBeingDragged = true;
if (deltaY > 0) {
deltaY -= mScaledTouchSlop;
} else {
deltaY += mScaledTouchSlop;
}
}*/
if (mIsBeingDragged) {
scrollBy(0, deltaY);
}
mLastMotionY = y;
break;
}
case MotionEvent.ACTION_UP: {
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity();
View lastChild = getChildAt(getChildCount() - 1);
//获取最大可滑动距离(最后一行View的Y值和高度,也就是lastChild底部到整个ViewGroup的距离减去View的绘制区域(屏幕除去状态栏和标题栏的剩下的内容的高度))
int lastChildH = (int) (lastChild.getY() + lastChild.getHeight() - display + paddingBottom);
//大于最小滑动速率,则进行惯性滑动
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
mScroller.fling(0, getScrollY(), 0, -initialVelocity, 0, 0, 0, lastChildH);
}
//超过顶部和底部则回弹
if (getScrollY() < 0)
scrollTo(0, 0);
if (getScrollY() >= lastChildH)
if (lastChildH < 0)
scrollTo(0, 0);
else
scrollTo(0, lastChildH);
mIsBeingDragged = false;
recycleVelocityTracker();
}
break;
}
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
}
return true;
} else return super.onTouchEvent(event);
}
private void initOrResetVelocityTracker() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
}
private void initVelocityTrackerIfNotExists() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
}
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
postInvalidate();
}
super.computeScroll();
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(this.getContext(), attrs);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
//获取图片集合
public List<String> getImageList() {
imageList.clear();
for (int i = 0; i < getChildCount(); i++) {
String string = getChildAt(i).getTag().toString();
if (!TextUtils.isEmpty(string)) {
imageList.add(getChildAt(i).getTag().toString());
}
}
return imageList;
}
public void addShowImage(String imageUrl, int index) {
addImage(imageUrl, index);
if (getChildCount() > defaultMaxPhotoNum) {
removeViewAt(getChildCount() - 1);
}
}
public static DisplayMetrics getScreenSize(Context context) {
DisplayMetrics metrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
return metrics;
}
public interface OnItemClickListener {
void addImageClick();
void lookImageClick(String imageUrl);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public void getPMHeight(int display) {
this.display = display;
}
public static int dip2px(Context context, float px) {
final float scale = getScreenDensity(context);
return (int) (px * scale + 0.5);
}
public static float getScreenDensity(Context context) {
return context.getResources().getDisplayMetrics().density;
}
}
demo下载地址
https://download.youkuaiyun.com/download/qq_31427095/10964914
参考链接
https://www.jianshu.com/p/4170cb0e8696