自定义View

2021-03-24  本文已影响0人  打工崽

自定义View案例实践

1. 继承自系统控件的自定义View

举一个简单例子,写一个自定义View继承自TextView

InvalidTextView

public class InvalidTextView extends androidx.appcompat.widget.AppCompatTextView {
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    public InvalidTextView(Context context){
        super(context);
        initDraw();
    }

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

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

    private void initDraw(){
        paint.setColor(Color.RED);
        paint.setStrokeWidth(1.5f);
    }

    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        canvas.drawLine(0, height / 2, width,height / 2,paint);
    }
}

activity_main

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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_height="match_parent"
    android:layout_width="match_parent"
    android:orientation="vertical">

    <com.example.viewtest.InvalidTextView
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:id="@+id/iv_text"
        android:background="@android:color/holo_blue_light"
        android:gravity="center"
        android:text="123"
        android:textSize="16sp"
        android:layout_centerHorizontal="true"/>

</RelativeLayout>

效果

image.png

2. 继承自View的自定义View

与上面的继承系统控件的自定义View不同,继承View的自定义View实现起来稍微复杂,其不只是要实现onDraw()方法,实现过程中还要考虑到wrap_content属性以及padding属性设置

为了方便配置自己的view,还会对外提供自定义的属性。另外如果要改变触控的逻辑,还要重写onTouchEvent()等触控事件的方法。

我们再写一个RectView类继承View来画一个正方形,代码如下

RectView

public class RectView extends View {
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private int mColor = Color.RED;

    public RectView(Context context){
        super(context);
        initDraw();
    }

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

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

    private void initDraw(){
        paint.setColor(mColor);
        paint.setStrokeWidth(1.5f);
    }

    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        canvas.drawRect(0,0, width, height, paint);
    }
}

activity_main

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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_height="match_parent"
    android:layout_width="match_parent"
    android:orientation="vertical">

    <com.example.viewtest.InvalidTextView
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:id="@+id/iv_text"
        android:background="@android:color/holo_blue_light"
        android:gravity="center"
        android:text="123"
        android:textSize="16sp"
        android:layout_centerHorizontal="true"/>

    <com.example.viewtest.RectView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:id="@+id/rv_rect"
        android:layout_below="@id/iv_text"
        android:layout_marginTop="50dp"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

效果

image.png

2.1 对padding属性进行处理

修改布局文件,给RectView加入padding属性

 android:padding="20dp"

运行后会发现没有任何效果,是因为我们还要修改onDraw()方法

public class RectView extends View {
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private int mColor = Color.RED;

    public RectView(Context context){
        super(context);
        initDraw();
    }

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

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

    private void initDraw(){
        paint.setColor(mColor);
        paint.setStrokeWidth(1.5f);
    }

    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() - paddingTop - paddingBottom;

        canvas.drawRect(0+paddingLeft,0+paddingTop, width+paddingLeft, height+paddingTop, paint);
    }
}

效果

image.png

2.2 对wrap_content属性进行处理

修改布局文件,让RectView的宽度分别为wrap_content和match_parent时效果都是一样的

效果

image.png

原因我们已经在之前已经说过了,在getChildMeasureSpec()方法里可以找到答案。这种情况我们需要在onMeasure()方法里指定一个默认的宽和高,在设置wrap_content属性时设置此默认的宽和高就可以了

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(400, 400);
        }else if(widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(400,heightSpecSize);
        }else if(heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize,400);
        }

    }

效果

image.png

2.3 自定义属性

Android系统的控件以android开头的都是系统自带的属性,为了方便配置RectView的属性,我们也可以自定义属性,首先在values目录下创建attrs.xml

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RectView">
        <attr name="rect_color" format="color"></attr>
    </declare-styleable>
    
</resources>

这个配置文件定义了名为RectView的自定义属性组合,我们定义了rect_color属性,它的格式为color,接下来在RectView的构造方法中解析自定义属性的值,如下

public RectView(Context context, AttributeSet attrs){
        super(context, attrs);
        TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.RectView);
        //提取RectView属性集合的rect_color属性,如果没设置默认值为Color.RED
        mColor = mTypedArray.getColor(R.styleable.RectView_rect_color,Color.RED);
        //获取资源后要及时回收
        mTypedArray.recycle();
        initDraw();
    }

用TypedArray来获取自定义属性集R.styleable.RectView,这个RectView就是我们在xml中定义的name值,然后通过TypedArray的getColor方法来获取自定义的属性值,最后修改RectView布局文件,加入两行代码

app:rect_color="@android:color/holo_blue_light"
xmlns:app="http://schemas.android.com/apk/res-auto"

使用自定义属性时需要添加xmlns:app="http://schemas.android.com/apk/res-auto"。其中app是我们的自定义名字,最后我们配置新定义的app:rect_color属性为android:color/holo_blue_light,我们发现效果变成了蓝色,如下

效果

image.png

3. 自定义组合控件

自定义组合控件就是多个控件合起来成为一个新的控件,其主要用于解决多次重复的使用同一类型的布局,比如我们应用的顶部标题栏及弹出固定样式的Dialog,这些都是常用的,所以把它们所需要的组合控件重新定义为一个新的控件,下面就来实践一个顶部标题栏

view_customtitle.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/iv_titlebar_rootlayout"
    android:layout_width="match_parent"
    android:layout_height="45dp">

    <ImageView
        android:id="@+id/iv_titlebar_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_centerInParent="true"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        android:src="@drawable/ic_baseline_arrow_back_ios_24"/>

    <TextView
        android:id="@+id/tv_titlebar_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:maxEms="11"
        android:singleLine="true"
        android:ellipsize="end"
        android:textStyle="bold"/>

    <ImageView
        android:id="@+id/iv_titlebar_right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerInParent="true"
        android:src="@drawable/ic_baseline_bedtime_24"
        android:gravity="center"
        android:padding="15dp"/>


</RelativeLayout>

其中ImageView的src文件为安卓系统自带图标,读者可以按需自行创建

image.png

接下来在res的values文件里创建attrs.xml

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TitleBar">
        <attr name="title_text_color" format="color"></attr>
        <attr name="title_bg" format="color"></attr>
        <attr name="title_text" format="string"></attr>

    </declare-styleable>

</resources>

我们定义了三个属性,分别用来设置顶部标题栏的背景颜色,标题文字颜色和标题文字,为了引入自定义属性,我们创建TitleBar类并在其构造方法里解析自定义属性的值

TitleBar

public class TitleBar extends RelativeLayout {
    private ImageView iv_titlebar_left;
    private ImageView iv_titlebar_right;
    private TextView tv_titlebar_title;
    private RelativeLayout layout_titlebar_rootlayout;
    private int mColor= Color.BLUE;
    private int mTextColor= Color.WHITE;
    public TitleBar(Context context) {
        super(context);
        initView(context);
    }

    public TitleBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray mTypedArray=context.obtainStyledAttributes(attrs,R.styleable.TitleBar);
        mColor=mTypedArray.getColor(R.styleable.TitleBar_title_bg,Color.BLUE);
        mTextColor=mTypedArray.getColor(R.styleable.TitleBar_title_text_color, Color.WHITE);
        String titlename=mTypedArray.getString(R.styleable.TitleBar_title_text);
        //获取资源后要及时回收
        mTypedArray.recycle();
        initView(context);
    }


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

    public void initView(Context context){
        LayoutInflater.from(context).inflate(R.layout.view_customtitle, this, true);
        iv_titlebar_left= (ImageView) findViewById(R.id.iv_titlebar_left);
        iv_titlebar_right= (ImageView) findViewById(R.id.iv_titlebar_right);
        tv_titlebar_title= (TextView) findViewById(R.id.tv_titlebar_title);
        layout_titlebar_rootlayout= (RelativeLayout) findViewById(R.id.layout_titlebar_rootlayout);
        //设置背景颜色
        layout_titlebar_rootlayout.setBackgroundColor(mColor);
        //设置标题文字颜色
        tv_titlebar_title.setTextColor(mTextColor);
    }
    public void setTitle(String titlename){
        if(!TextUtils.isEmpty(titlename)) {
            tv_titlebar_title.setText(titlename);
        }
    }

    public void setLeftListener(OnClickListener onClickListener){
        iv_titlebar_left.setOnClickListener(onClickListener);
    }
    public void setRightListener(OnClickListener onClickListener){
        iv_titlebar_right.setOnClickListener(onClickListener);
    }
}

接下来引用组合控件的布局

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.viewcombinetest.TitleBar
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        app:title_text="自定义组合控件"
        app:title_bg="@android:color/holo_orange_dark"
        app:title_text_color="@android:color/holo_blue_dark" />

</LinearLayout>

最后,主界面调用自定义TitleBar

MainActivity

public class MainActivity extends Activity {
    private TitleBar mTitleBar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTitleBar= (TitleBar) this.findViewById(R.id.title);
//      mTitleBar.setTitle("自定义组合控件");

        mTitleBar.setLeftListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "点击左键", Toast.LENGTH_SHORT).show();
            }
        });

        mTitleBar.setRightListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "点击右键", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

效果

image.png

4. 自定义ViewGroup

自定义ViewGroup又分为继承ViewGroup和继承系统特定的ViewGroup,比如RelativeLayout,其中继承系统特定的ViewGroup比较简单,这里不做介绍。主要介绍继承ViewGroup,以下的例子是一个自定义的ViewGroup,左右滑动切换不同的页面,类似一个特别简化的ViewPager


4.1继承ViewGroup

要实现自定义ViewGroup,首先要继承ViewGroup并调用父类构造方法,实现抽象方法等

HorizontalView

import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
public class HorizontalView extends ViewGroup{
    public HorizontalView(Context context) {
        super(context);
    }
    public HorizontalView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public HorizontalView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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


4.2 对wrap_content属性进行处理

在上面的类中继续加入方法对wrap_content属性做特殊处理

HorizontalView

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //如果没有子元素,就设置宽高都为0(简化处理)
        if (getChildCount() == 0) { 
            setMeasuredDimension(0, 0);
        }
        //宽和高都是AT_MOST,则设置宽度所有子元素的宽度的和;高度设置为第一个元素的高度;
        else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            View childOne = getChildAt(0);
            int childWidth = childOne.getMeasuredWidth();
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(childWidth * getChildCount(), childHeight);
        }
        //如果宽度是wrap_content,则宽度为所有子元素的宽度的和
        else if (widthMode == MeasureSpec.AT_MOST) {
            int childWidth = getChildAt(0).getMeasuredWidth();
            setMeasuredDimension(childWidth * getChildCount(), heightSize);
        }
        //如果高度是wrap_content,则高度为第一个子元素的高度
        else if (heightMode == MeasureSpec.AT_MOST) {
            int childHeight = getChildAt(0).getMeasuredHeight();
            setMeasuredDimension(widthSize, childHeight);
        }

这里如果没有子元素时采用了简化的写法直接将宽和高直接设置为0,正常的话我们应该根据LayoutParams中的宽和高来做相应的处理,另外我们在测量时没有考虑它的padding和子元素的margin。


4.3 实现onLayout()方法

丰富layout()方法来布局子元素,因为每一种布局方式子View的布局都是不同的,所以这个是ViewGroup唯一一个抽象方法,需要我们自己去实现:

HorizontalView的layout()方法

@Override
       protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int left = 0;
        View child;
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                int width = child.getMeasuredWidth();
                childWidth = width; 
                child.layout(left, 0, left + width, child.getMeasuredHeight());
                left += width;
            }
        }

遍历所有的子元素,如果子元素不是GONE,则调用子元素的layout方法将其放置到合适的位置上,相当于默认第一个子元素占满了屏幕,后面的子元素就是在第一个屏幕后面紧挨着和屏幕一样大小的后续元素,所以left是一直累加的,top保持0,bottom保持第一个元素的高度,right就是left+元素的宽度,同样这里没有处理自身的pading以及子元素的margin。


4.4 处理滑动冲突

这个自定义ViewGroup是水平滑动,如果里面是ListView,则ListView是垂直滑动,如果我们检测到的滑动方向是水平的话,就让父View拦截用来进行View的滑动切换

onInterceptTouchEvent()方法

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - lastInterceptX; 
                int deltaY = y - lastInterceptY; 
                //用户想水平滑动的,所以拦截
                if (Math.abs(deltaX) - Math.abs(deltaY) > 0) { 
                    intercept = true; 
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        lastX = x;
        lastY = y;
        lastInterceptX = x; 
        lastInterceptY = y;
        return intercept;
    }

4.5 弹性滑动到其他页面

这里就会进入onTouchEvent事件,然后我们需要进行滑动切换页面,这里需要用到Scroller

public class HorizontalView extends ViewGroup {
    //... 省略构造函数,init方法,onInterceptTouchEvent
    int lastInterceptX;
    int lastInterceptY;
    int lastX;
    int lastY;
    int currentIndex = 0; //当前子元素
    int childWidth = 0; 
    private Scroller scroller;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - lastX; //跟随手指滑动
                scrollBy(-deltaX, 0);
                break;
            case MotionEvent.ACTION_UP: 
             //相对于当前View滑动的距离,正为向左,负为向右
                int distance = getScrollX() - currentIndex * childWidth;
                //滑动的距离要大于1/2个宽度,否则不会切换到其他页面
                if (Math.abs(distance) > childWidth / 2) {
                    if (distance > 0) {
                        currentIndex++;
                    } else {
                        currentIndex--;
                    }
                }
                smoothScrollTo(currentIndex * childWidth, 0);
                break;
        }
        lastX = x;
        lastY = y;
        return super.onTouchEvent(event);
    }
    //...省略onMeasure方法
     @Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            postInvalidate();
        }
    }
    //弹性滑动到指定位置
    public void smoothScrollTo(int destX, int destY) {
        scroller.startScroll(getScrollX(), getScrollY(), destX - getScrollX(), destY - getScrollY(), 1000); 
        invalidate();
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int left = 0; 
        View child;
        //遍历布局子元素
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            int width = child.getMeasuredWidth();
            //赋值为子元素的宽度
            childWidth = width; 
            child.layout(left, 0, left + width, child.getMeasuredHeight());
            left += width;
        }
    }
}

4.6 快速滑动到其他页面

我们不只滑动超过一半才切换到上/下一个页面,如果滑动速度很快的话,我们也可以判定为用户想要滑动到其他页面,这样的体验也是好的。 这部分也是在onTouchEvent中的ACTION_UP部分:
这里又需要用到VelocityTracker,它用来测试滑动速度的。使用方法也很简单,首先在构造函数中进行初始化,也就是前面的init方法中增加一条语句

init()

private VelocityTracker tracker;    
  ...
  public void init() {
        scroller = new Scroller(getContext());
        tracker=VelocityTracker.obtain();
    }

接着改写onTouchEvent()部分:

onTouchEvent()

@Override
    public boolean onTouchEvent(MotionEvent event) {
...
  case MotionEvent.ACTION_UP:
              //相对于当前View滑动的距离,正为向左,负为向右
                int distance = getScrollX() - currentIndex * childWidth; 
                //必须滑动的距离要大于1/2个宽度,否则不会切换到其他页面
                if (Math.abs(distance) > childWidth / 2) {
                    if (distance > 0) {
                        currentIndex++;
                    } else {
                        currentIndex--;
                    }
                }
                else {
                //调用该方法计算1000ms内滑动的平均速度   
                 tracker.computeCurrentVelocity(1000);
                    float xV = tracker.getXVelocity(); //获取到水平方向上的速度
                    //如果速度的绝对值大于50的话,就认为是快速滑动,就执行切换页面
                    if (Math.abs(xV) > 50) { 
                    //大于0切换上一个页面
                        if (xV > 0) { 
                            currentIndex--;
                    //小于0切换到下一个页面
                        } else { 
                            currentIndex++;
                        }
                    }
                }
                currentIndex = currentIndex < 0 ? 0 : currentIndex > getChildCount() - 1 ? getChildCount() - 1 : currentIndex;
                smoothScrollTo(currentIndex * childWidth, 0);
                //重置速度计算器
                tracker.clear();
                break;
             }

4.7 再次触摸屏幕防止页面继续滑动

当我们快速向左滑动切换到下一个页面的情况,在手指释放以后,页面会弹性滑动到下一个页面,可能需要一秒才完成滑动,这个时间内,我们再次触摸屏幕,希望能拦截这次滑动,然后再次去操作页面

要实现在弹性滑动过程中再次触摸拦截,肯定要在onInterceptTouchEvent中的ACTION_DOWN中去判断,如果在ACTION_DOWN的时候,scroller还没有完成,说明上一次的滑动还正在进行中,则直接中断scroller

onInterceptTouchEvent()

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        boolean intercept = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: 
                intercept = false;

               //如果动画还没有执行完成,则打断
                if (!scroller.isFinished()) {
                    scroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:          
                int deltaX = x - lastInterceptX;
                int deltaY = y - lastInterceptY;
                if (Math.abs(deltaX) - Math.abs(deltaY) > 0) { 
                    intercept = true;
                } else {
                    intercept = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                break;
        }
        //因为DOWN返回false,所以onTouchEvent中无法获取DOWN事件,所以这里要负责设置lastX,lastY
        lastX = x;
        lastY = y;
        lastInterceptX = x;
        lastInterceptY = y;
        return intercept;
    }

4.8 引用HorizontalView布局

activity_main.xml

<RelativeLayout 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.example.viewgroupcustomtest.HorizontalView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ListView
            android:id="@+id/lv_one"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <ListView
            android:id="@+id/lv_two"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </com.example.viewgroupcustomtest.HorizontalView>


</RelativeLayout>

接着,在代码中为ListView添加数据

MainActivity

public class MainActivity extends AppCompatActivity {
    private ListView lv_one;
    private ListView lv_two;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lv_one=(ListView)this.findViewById(R.id.lv_one);
        lv_two=(ListView)this.findViewById(R.id.lv_two);
        String[] strs1 = {"1","2","3","4","5","6","7","8","9","10","11","12","13","14","15"};
        ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(this,android.R.layout.simple_expandable_list_item_1,strs1);
        lv_one.setAdapter(adapter1);

        String[] strs2 = {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O"};
        ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(this,android.R.layout.simple_expandable_list_item_1,strs2);
        lv_two.setAdapter(adapter2);
    }
}

效果

image.png

这样就完成了自定义ViewGroup的实现


本文摘抄自《Android进阶之光——刘望舒》,为自己学习路程中的记录,不以盈利为目的。


欢迎指正。

上一篇下一篇

猜你喜欢

热点阅读