【Android】Android自定义ViewGroup

本文介绍如何通过自定义ViewGroup实现粘性滚动效果。主要步骤包括重写onMeasure()方法测量子View尺寸,onLayout()方法定位子View,及onTouchEvent()方法处理触摸事件。示例代码展示了具体实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ViewGroup存在的目的就是对其子View进行管理,为其子View添加显示、响应的规则。因此,自定义ViewGroup通常需要重写onMeasure()方法对其子View进行测量,重写onLayout()方法来确定子View的位置,重写onTouchEvent()方法增加响应事件。所以我们需要做这几件事:

  • 重写onMeasure()方法对其子View进行测量
  • 重写onLayout()方法来确定子View的位置
  • 重写onTouchEvent()方法增加响应事件

以一个粘性View效果作为示例,来看看每部分都需要怎么做。先看完整版代码,后面会进一步分析:

package com.wondertwo.app.customview;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
 * 实现粘性效果,即当子View向上滑动大于某值后,松开手指它将向上滑动并显示下一个子View
 * 当滑动距离小于某值,松开手指后它将回到开始的位置
 *
 * Created by wondertwo on 2016/2/27.
 */
public class SticklyView extends ViewGroup {

private int mScreenHeight;
private Scroller mScroller;
private int mLastY;
private int mStart;
private int mEnd;

public SticklyView(Context context) {
    super(context);
}

public SticklyView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public SticklyView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

/**
 * 重写onMeasure()方法,通过遍历子View来测量子View的大小
 *
 * @param widthMeasureSpec
 * @param heightMeasureSpec
 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int count = getChildCount();
    for (int i = 0; i < count; ++i) {
        View childView = getChildAt(i);
        measureChild(childView, widthMeasureSpec, heightMeasureSpec);
    }
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childCount  = getChildCount();
    // 设置ViewGroup的高度,高度等于每个子View的高度乘以子View的个数
    MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
    mlp.height = mScreenHeight * childCount;
    setLayoutParams(mlp);
    for (int i = 0; i< childCount; i++) {
        View child = getChildAt(i);
        if (child.getVisibility() != View.GONE) {
            // public void layout(int left, int top, int right, int bottom)设置child的位置
            child.layout(l, i * mScreenHeight, r, (i + 1) *mScreenHeight);
        }
    }
}

/**
 * 手指触摸动作的监听
 *
 * @param event
 * @return
 */
@Override
public boolean onTouchEvent(MotionEvent event) {
    int y = (int) event.getY();
    switch (event.getAction()) {
        // 记录触摸起点
        case MotionEvent.ACTION_DOWN:
            mLastY = y;
            mStart = getScrollY();
            break;
        case MotionEvent.ACTION_MOVE:
            // 拦截动画
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }
            int dy = mLastY - y;
            if (getScrollY() < 0) {
                dy = 0;
            }
            if (getScrollY() > getHeight() - mScreenHeight) {
                dy = 0;
            }
            scrollBy(0, dy);
            mLastY = y;
            break;
        case MotionEvent.ACTION_UP:
            // 记录触摸终点
            int dScrollY = checkAlignment();
            if (dScrollY > 0) {
                if (dScrollY < mScreenHeight / 3) {
                    mScroller.startScroll(
                            0, getScrollY(),
                            0, -dScrollY);
                } else {
                    mScroller.startScroll(
                            0, getScrollY(),
                            0, mScreenHeight - dScrollY);
                }
            } else {
                if (-dScrollY < mScreenHeight / 3) {
                    mScroller.startScroll(
                            0, getScrollY(),
                            0, -dScrollY);
                } else {
                    mScroller.startScroll(
                            0, getScrollY(),
                            0, -mScreenHeight - dScrollY);
                }
            }
            break;
    }
    // 重绘
    postInvalidate();
    return true;
}

@Override
public void computeScroll() {
    super.computeScroll();
    if (mScroller.computeScrollOffset()) {
        scrollTo(0, mScroller.getCurrY());
        postInvalidate();
    }
}

private int checkAlignment() {
    int mEnd = getScrollY();
    boolean isUp = ((mEnd - mStart) > 0) ? true : false;
    int lastPrev = mEnd % mScreenHeight;
    int lastNext = mScreenHeight - lastPrev;
    if (isUp) {
        //向上的
        return lastPrev;
    } else {
        return -lastNext;
    }
  }
}
  • 重写onMeasure()方法对其子View进行测量先要获得子View的数量:int count = getChildCount()。然后通过for循环遍历每个子View获得他们的大小。代码如下:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childCount  = getChildCount();
    // 设置ViewGroup的高度,高度等于每个子View的高度乘以子View的个数
    MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
    mlp.height = mScreenHeight * childCount;
    setLayoutParams(mlp);
    for (int i = 0; i< childCount; i++) {
        View child = getChildAt(i);
        if (child.getVisibility() != View.GONE) {
            // public void layout(int left, int top, int right, int bottom)设置child的位置
            child.layout(l, i * mScreenHeight, r, (i + 1) *mScreenHeight);
        }
    }
    }
    
  • 重写onLayout()方法来确定子View的位置和重写onTouchEvent()方法增加响应事件了。其实这一步才是实现粘效果的关键。代码如下:

    /**
     * 手指触摸动作的监听
     *
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
       int y = (int) event.getY();
        switch (event.getAction()) {
        // 记录触摸起点
        case MotionEvent.ACTION_DOWN:
            mLastY = y;
            mStart = getScrollY();
            break;
        case MotionEvent.ACTION_MOVE:
            // 拦截动画
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }
            int dy = mLastY - y;
            if (getScrollY() < 0) {
                dy = 0;
            }
            if (getScrollY() > getHeight() - mScreenHeight) {
                dy = 0;
            }
            scrollBy(0, dy);
            mLastY = y;
            break;
        case MotionEvent.ACTION_UP:
            // 记录触摸终点
            int dScrollY = checkAlignment();
            if (dScrollY > 0) {
                if (dScrollY < mScreenHeight / 3) {
                    mScroller.startScroll(
                            0, getScrollY(),
                            0, -dScrollY);
                } else {
                    mScroller.startScroll(
                            0, getScrollY(),
                            0, mScreenHeight - dScrollY);
                }
            } else {
                if (-dScrollY < mScreenHeight / 3) {
                    mScroller.startScroll(
                            0, getScrollY(),
                            0, -dScrollY);
                } else {
                    mScroller.startScroll(
                            0, getScrollY(),
                            0, -mScreenHeight - dScrollY);
                }
            }
            break;
        }
    // 重绘
    postInvalidate();
    return true;
    }
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值