自定义View(五)之继承ViewGroup

一,概述

在android系统中可以把View分为两大类,一类是View,这类View本身具有展示UI的作用,比如TextView可以展示文本,ImageView可以展示图片,EditText可以编辑文字。另外一类是ViewGroup,即容器View,ViewGroup本质是容器,用来盛放View,并决定View的摆放位置,这类View如Linearlayout和RelativeLayout。

当我们需要一个容器来盛放现成的View,并需要View按照一定的要求摆放时我们需要继承ViewGroup。例如侧滑菜单,菜单部分和主页面部分都可以单独实现,但他们的摆放状态和滑动不能相互关联,此时就需要自定义容器View。下面就以侧滑菜单为例说明继承ViewGroup的自定义View的实现。

二,继承ViewGroup实现自定义View的步骤

直接上代码,注释写在代码中。

1,自定义一个类SlidingMenu,继承View,并重写构造方法

public class SlidingMenu extends ViewGroup {
    public SlidingMenu(Context context) {
        super(context);
    }

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

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

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }
}

注意事项:
1,由于ViewGroup的onLayout方法是抽象方法,所以必须重写。onLayout方法很重要,它决定子view的摆放位置,要着重注意。
2,重写三个构造方法。这三个构造方法的作用及使用场合与继承View时的三个构造方法完全相同。

2,在布局文件中使用SlidingMenu

因为SlidingMenu是容器View,与Linearlayout同一类型,所以用法也类似于Linearlayout,在SlidingMenu中写上子View,如下:

<honor.com.slidingmenu.view.SlidingMenu
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!--菜单页面的View-->
    <TextView
        android:id="@+id/menu_page"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:text="菜单"
        android:background="#f00"
        android:textSize="30sp"
        android:textColor="#000"/>
    <!--主页面的View-->
    <TextView
        android:id="@+id/main_page"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:text="主页面"
        android:background="#ff0"
        android:textSize="30sp"
        android:textColor="#000"/>
</honor.com.slidingmenu.view.SlidingMenu>

注意事项:
1,由于容器View一般不会单独使用,所以在使用ViewGroup自定义容器View时要结合View一块使用。
2,此时SlidingMenu是容器View,里面可以放任意类型的View,包括Linearlayout或RelativeLayout等容器View。这儿放了两个TextView是为了简化代码,便于说明问题。
3,菜单View高度充满全屏,宽度为240dp,背景颜色设为红色,便于与主页面区分。
4,主页面View高度和宽度都充满屏幕,正常显示时只显示主页面,滑动时才会显示出菜单页面。

3,重写SlidingMenu的onFinishInflate方法,在这个方法中获取menuView和mainView

这一步的作用是获取menuView和mainView。代码如下:

    protected void onFinishInflate() {
        super.onFinishInflate();
        menuView = getChildAt(0);
        mainView = getChildAt(1);
    }

说明:
1,onFinishInflate是当解析布局文件完成,并生成View对象后调用的方法,此时可以通过getChildAt方法得到对应的子View。
2,getchildAt方法是根据索引获取子View对象,这个索引对应布局文件中的顺序,所以索引为0就是菜单View,索引为1就是主页面View。
3,getChildAt方法不能在构造方法中使用,因为构造方法执行时只是创建了SlidingMenu对象,并没有把子View对象放入到容器中,所以必须在布局文件解析完成后才能使用。当然在onMeasure方法和onLayout方法中都可以使用。

4,重写onMeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //从布局文件中得到菜单view的宽度
    menuViewWidth = menuView.getLayoutParams().width;
    //指定menuView的宽度测量模式为MeasureSpec.EXACTLY
    int menuViewWidthMeasereMode = MeasureSpec.EXACTLY;
    //根据宽度和测量模式得到宽度测量规则
    int menuWidthMeasureSpec = MeasureSpec.makeMeasureSpec(menuViewWidth, menuViewWidthMeasereMode);
    //测量menuView
    menuView.measure(menuWidthMeasureSpec, heightMeasureSpec);

    // 测量mainView
    mainView.measure(widthMeasureSpec, heightMeasureSpec);
}

说明:
1,这个方法中做了两件事,一是调用super.onMeasure方法测量自己。二是调用子view的measure方法测量子View。
2,由于在布局文件中给SlidingMenu设置的宽高都是match_parent,所以onMeasure方法传递过来的测量规则都是充满屏幕,所以对于测量自己可以直接把这两个参数传递进去。
3,我们希望显示的menuView的宽是240dp,所以这个宽度测量规则要根据布局文件中设置的宽度生成测量规则。对于menuView的高希望是充满屏幕,此时直接使用了sliddingMenu的高度测量规则,当然我们也可以获取menuView在布局文件中设置的高度,但效果是一样的,所以直接使用了slidingMenu的高度测量规则。
4,对于mainView,我们希望高宽都充满屏幕,所以高宽都直接使用SlidingMenu的高宽测量规则。

5,重写onLayout方法

protected void onLayout(boolean changed, int l, int t, int r, int b) {
        menuViewLeft = l - menuViewWidth;//得到menuView的左侧坐标
        int menuViewRight = menuViewLeft + menuViewWidth;//得到menuView的右侧坐标
        menuView.layout(menuViewLeft,t,menuViewRight,b);//设置menuView的上、下、左、右四个坐标。

        int mainViewLeft = menuViewRight;//得到menuView的左侧坐标
        int mainViewRight = menuViewRight + r;//得到menuView的右侧坐标
        mainView.layout(mainViewLeft,t,mainViewRight,b);//设置menuView的上、下、左、右四个坐标。
    }

说明:
1,onLayout方法的作用是布局子View,自己的布局不在onLayout方法中。
2,布局子view即调用子View的layout方法,传递进入上、下、左、右四个坐标。
3,onLayout方法传递过来四个int类型的值,分别是SlidingMenu的上下左右坐标,由于slidingMenu是充满屏幕的,所以SlidingMenu的上下左右坐标分别对应屏幕的上下左右坐标。
4,menuView在默认状态下的右侧坐标在屏幕的左侧,所以左侧坐标就是l - menuViewWidth.
5,mainView在默认状态下是全屏显示。但是侧滑菜单时需要滑动的,所以不能直接使用SlidingMenu的上下左右坐标。mainView要随menuView的位置改变而改变,所以mainView的左侧坐标应该等于menuView的右侧坐标。

截止到此处,基本功能已经完成,可以运行了。但是运行结果只能显示出mainView,而看不到menuView。因为menuView在屏幕的左侧,只有滑动时才能显示出来。下面写滑动的逻辑。

6,重写onTouchEvent实现菜单滑动

    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();//记录按下的位置
                break;
            case MotionEvent.ACTION_MOVE:
                distanceX = (int) (event.getX()-downX+ oldDistanceX);//得到滑动的距离
                if(distanceX < 0){//为了避免右侧滑出边界
                    distanceX=0;
                }else if(distanceX > menuViewWidth){//为了避免右侧滑出边界
                    distanceX = menuViewWidth;
                }
                scrollXTo(distanceX);//滑动view
                break;
            case MotionEvent.ACTION_UP:
                if(distanceX < menuViewWidth/2){//当手指抬起时偏向于主页面,就退出菜单
                    distanceX=0;
                }else {
                    distanceX = menuViewWidth;//否则就完全滑出菜单
                }
                scrollXTo(distanceX);//滑动View
                oldDistanceX = distanceX;
                break;
        }
        return true;
    }

    /**
     * 封装view的scrollTo方法。
     */
    private void scrollXTo(float x){
        scrollTo((int) -x,0);
    }

此时即可滑动菜单了。

猜你喜欢

转载自blog.csdn.net/fightingxia/article/details/72670125