美文网首页
自定义布局实践

自定义布局实践

作者: hdychi | 来源:发表于2018-08-09 12:45 被阅读0次

    一、简介

    实现了一个可以将子view在水平方向上等间距排列的布局,支持水平滚动。

    效果如下:

    屏幕快照 2018-08-09 上午11.46.13.png

    二、实现

    自定义布局CarouselLayout.java:

    package com.hdychi.carousel.layout;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.ViewGroup;
    
    import com.hdychi.carousel.R;
    
    public class CarouselLayout extends ViewGroup {
        private static final int DEFAULT_WIDTH = 50;
        private static final int DEFULT_ITEM_COUNT = 5;
        private static final int TOUCH_SLOP = 50;
        private int visibleItem = 5;
    
        public CarouselLayout(Context context) {
            super(context);
        }
    
        public CarouselLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context,attrs);
        }
    
        public CarouselLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context,attrs);
        }
    
        public CarouselLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            init(context,attrs);
        }
        public void init(Context context,AttributeSet attrs) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CarouselLayout);
            if (typedArray != null) {
                visibleItem = typedArray.getInt(R.styleable.CarouselLayout_visibleItem, DEFULT_ITEM_COUNT);
                typedArray.recycle();
            }
        }
    
        /**
         * 测量阶段,按照屏幕内可见的子view数量计算子view的最大宽度
         * @param widthMeasureSpec
         * @param heightMeasureSpec
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int selfHeightMode = MeasureSpec.getMode(heightMeasureSpec);
            int selfHeightSize = MeasureSpec.getSize(heightMeasureSpec);
            int selfWidthMode = MeasureSpec.getMode(widthMeasureSpec);
            int selfWidthSize = MeasureSpec.getSize(widthMeasureSpec);
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                LayoutParams layoutParams = child.getLayoutParams();
                int childHeightSpec;
                int childWidthSpec;
                switch (layoutParams.height) {
                    case LayoutParams.MATCH_PARENT:
                        if (selfHeightMode == MeasureSpec.EXACTLY
                                || selfHeightMode == MeasureSpec.AT_MOST) {
                            childHeightSpec = MeasureSpec.makeMeasureSpec(selfHeightSize,
                                    MeasureSpec.EXACTLY);
                        } else {
                            childHeightSpec = MeasureSpec.makeMeasureSpec(selfHeightSize,
                                    MeasureSpec.AT_MOST);
                        }
                        break;
                    case LayoutParams.WRAP_CONTENT:
                        if (selfHeightMode == MeasureSpec.EXACTLY
                                || selfHeightMode == MeasureSpec.AT_MOST) {
                            childHeightSpec = MeasureSpec.makeMeasureSpec(selfHeightSize,
                                    MeasureSpec.AT_MOST);
                        } else {
                            childHeightSpec = MeasureSpec.makeMeasureSpec(selfHeightSize,
                                    MeasureSpec.UNSPECIFIED);
                        }
                        break;
                    default:
                        childHeightSpec = MeasureSpec.makeMeasureSpec(selfHeightSize, MeasureSpec.EXACTLY);
                        break;
                }
                childWidthSpec = MeasureSpec.makeMeasureSpec(selfWidthSize / visibleItem, MeasureSpec.AT_MOST);
                measureChild(child, childWidthSpec, childHeightSpec);
            }
            int maxHeight = selfHeightSize;
            for (int i = 0;i < getChildCount();i++) {
                maxHeight = Math.max(maxHeight,getChildAt(i).getMeasuredHeight());
            }
            setMeasuredDimension(selfWidthSize,selfHeightMode == MeasureSpec.EXACTLY?
                    selfHeightSize:maxHeight);
        }
    
        /**
         * 从左到右安排子view,每个子 view相当于在一个等分的block里
         * @param changed
         * @param left
         * @param top
         * @param right
         * @param bottom
         */
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            int childCount = getChildCount();
            int selfWidth = getWidth();
            int selfHeight = getHeight();
            int blockWidth = selfWidth / visibleItem;
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                int childWidth = child.getMeasuredWidth();
                int childHeight = child.getMeasuredHeight();
                child.layout(i * blockWidth + (blockWidth - childWidth) / 2,
                        (selfHeight - childHeight) / 2,
                        i * blockWidth + (blockWidth - childWidth) / 2 + childWidth,
                        selfHeight - (selfHeight - childHeight) / 2);
            }
        }
    
        float lastX,lastY;
        float firstX,firstY;
        boolean isMoving;
    
        /**
         * 监听触摸事件,根据滑动距离滚动view,要滑出子view分布范围时不再滑动
         * @param event
         * @return
         */
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    firstX = event.getRawX();
                    firstY = event.getRawY();
                    lastX = event.getRawX();
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (!isMoving) {
                        if (Math.abs(event.getRawX() - firstX) > Math.abs(event.getRawY() - firstY)
                                && Math.abs(event.getRawX() - firstX) > TOUCH_SLOP) {
                            isMoving = true;
                        }
                        lastX = event.getRawX();
                    }
                    if (isMoving) {
                        float offset = event.getRawX() - lastX;
                        if (getScrollX() - offset < 0) {
                            if (offset > 0) {
                                offset = 0;
                            }
                        }
                        if (getScrollX() - offset > getChildCount() * getWidth() / visibleItem - getWidth()) {
                            if (offset < 0) {
                                offset = 0;
                            }
                        }
                        scrollBy(-(int)offset,0);
                    }
                    lastX = event.getRawX();
                    lastY = event.getRawY();
                    break;
                 case MotionEvent.ACTION_CANCEL:
                     isMoving = false;
                     break;
            }
           return true;
        }
    }
    
    

    添加自定义view的属性,在res/values下的attr.xml中添加:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="CarouselLayout">
            <attr name="visibleItem" format="integer"/>
        </declare-styleable>
    </resources>
    

    表示屏幕内可见的item有多少个。

    在布局文件中使用:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <com.hdychi.carousel.layout.CarouselLayout
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:text="Hello World!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:visibleItem="3">
            <include layout="@layout/carousel_item"/>
            <include layout="@layout/carousel_item"/>
            <include layout="@layout/carousel_item"/>
            <include layout="@layout/carousel_item"/>
            <include layout="@layout/carousel_item"/>
        </com.hdychi.carousel.layout.CarouselLayout>
    
    </android.support.constraint.ConstraintLayout>
    

    相关文章

      网友评论

          本文标题:自定义布局实践

          本文链接:https://www.haomeiwen.com/subject/gsbqbftx.html