自定义控件

android 自定义控件分为两种:

  1. 通过view或者viewGroup重新onMeasure和onDraw实现指定UI的控件
  2. 通过包装xml布局文件实现特定功能的UI控件

方式一:自绘控件

使用自绘控件首先要具备基本的自绘知识,如果没有请自行前往补习。我们使用app的时候经常会记录搜索记录,界面如下:在这里插入图片描述
首先该控件需要满足条件:如果当前行可以展示当前历史记录,则在一行展示,如果不满足则换行展示,所以需要实现高度wrap_content模式。
主要思路:

  1. 每条记录Item的高度是固定的,所以只需要知道某一条高度即可
  2. 在onMeasure里面优先计算子控件的高度和宽度,GONE模式的不计算
  3. 通过for循环计算容纳当前数量子控件的高度和宽度,从而推算出当前容器的高度
  4. 在onLayout里面对子控件进行布局
  5. 因为历史记录展示往往会限制数量,例如我们限制最多显示两行的历史记录
public class WrapContentControl extends ViewGroup {
    public static interface IWarpItemClick{
        public void onWarpItemClick(String text);
    }
    private int mMaxRow = 2;
    public WrapContentControl(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
		
		//计算每个子控件的高度和宽度
        int nChildeCount = getChildCount();
        for (int i = 0;i < nChildeCount;++i){
            View child = getChildAt(i);
            if(child.getVisibility() != View.GONE){
                measureChild(child,widthMeasureSpec,heightMeasureSpec);
            }
        }

        //计算容纳子控件需要的高度
        int nItemHeight = 0;//Utils.dp2px(getContext(),35);
        int nOffsetLeft = 0;
        int nRow = 1;
        for (int i = 0;i < nChildeCount;++i){
            View child = getChildAt(i);
            if(child.getVisibility() == View.GONE)
                continue;

            if(nItemHeight == 0)
                nItemHeight = child.getMeasuredHeight();

            if(nOffsetLeft + child.getMeasuredWidth() > wSpecSize ){
                nOffsetLeft = 0;
                nRow += 1;
            }

            nOffsetLeft += child.getMeasuredWidth();

            if(nRow >= mMaxRow)
                break;
        }

        int nHeight = nRow*nItemHeight;
        //设置当前容器的宽度和高度
        setMeasuredDimension(wSpecSize,nHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int nChildCount = getChildCount();
        int nOffsetLeft = 0;
        int nOffsetTop = 0;
        int nRow = 1;
        for (int i = 0;i < nChildCount;++i){
            View child = getChildAt(i);
            if(child.getVisibility() == View.GONE)
                continue;

            if(nOffsetLeft + child.getMeasuredWidth() > getWidth() ){
                nOffsetLeft = 0;
                nRow += 1;
                nOffsetTop += (getHeight()/mMaxRow) *(nRow - 1);
            }
			//对子控件位置进行布局
            child.layout(nOffsetLeft, nOffsetTop, nOffsetLeft + child.getMeasuredWidth(), nOffsetTop + child.getMeasuredHeight());
            nOffsetLeft += child.getMeasuredWidth();
        }
    }   
}

由于篇幅限制这里只做控件的自绘讲解,并不做数据更新等Adapter的适配讲解。如果偷懒的可以预先固定显示20个标签,然后根据数据动态更新内容和做数据显示即可。如下:

<com.stnts.rocket.Control.WrapContentControl
            android:id="@+id/history_pannel"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <include layout="@layout/his_label_view" />
			....这里一共20个item....
        </com.stnts.rocket.Control.WrapContentControl>

根据数据表实时更新数据

 public void update(List<String> histList){
        //LayoutInflater.from(getContext()).inflate(R.layout.his_label_view,this,true);
        int nCount = getChildCount();
        for (int i = 0;i < histList.size();++i){
            if(i < nCount){
                View itemview = getChildAt(i);
                itemview.setVisibility(VISIBLE);
                TextView textView = itemview.findViewById(R.id.label_item);
                textView.setText(histList.get(i));
            }
        }

        for (int i = histList.size();i < nCount;++i){
            View itemview = getChildAt(i);
            itemview.setVisibility(GONE);
        }
    }

当然为了控件的实用性,建议写一个Adapter动态适配。

方式二:布局封装控件
主要思路:

  1. 通过继承ViewGroup或者其子类加载xml文件
  2. 在继承的类里面实现特定功能封装
  3. 按照一般控件使用即可(可以自定义属性

下面我们以封装一个带有返回功能的导航栏:
在这里插入图片描述
布局文件:

<?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="60dp">

    <ImageView
        android:id="@+id/back_control"
        android:layout_height="30dp"
        android:layout_width="30dp"
        android:layout_gravity="center"
        android:background="@drawable/icon_back"/>

    <TextView
        android:id="@+id/title_control"
        android:layout_height="match_parent"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:text="导航标题"
        android:gravity="center"
        android:textSize="20sp"
        android:textColor="@color/dark_sep"
        android:singleLine="true" />

    <View
        android:layout_height="30dp"
        android:layout_width="30dp"/>

</LinearLayout>

自定义控件

public class TitleBar extends RelativeLayout {

    private TextView mTextView = null;
    private ImageView mBackControl = null;
    private Context mContext;

    public TitleBar(Context context) {
        super(context);
        mContext = context;
    }

    public TitleBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init(context,attrs);
    }

    public TitleBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init(context,attrs);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public TitleBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mContext = context;
        init(context,attrs);
    }

    void init(Context context,AttributeSet attrs) {
        View view = View.inflate(context, R.layout.title,this);
        TypedArray at = context.obtainStyledAttributes(attrs,R.styleable.TitleBar);

        mTextView = view.findViewById(R.id.title_control);
        mBackControl = view.findViewById(R.id.back_control);
        mTextView.setText(at.getString(R.styleable.TitleBar_title));

        mBackControl.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    ((Activity)mContext).finish();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
    }

    public void setTitle(String title){
        if(mTextView != null){
            mTextView.setText(title);
        }
    }

}

使用:
在这里插入图片描述
最终效果
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值