自定义布局实践

2018-08-09  本文已影响0人  hdychi

一、简介

实现了一个可以将子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>
上一篇下一篇

猜你喜欢

热点阅读