自定义九宫格图片

注:此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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值