自定义控件01

1.autoCompleteTextview

[1]在布局中声明

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.itheima.myapplication.MainActivity">
    <AutoCompleteTextView
        android:id="@+id/actv"
        android:layout_width="match_parent"
        android:completionThreshold="1"
        android:layout_height="wrap_content" />
</RelativeLayout>

[2]这个控件展示数据原理和listview一样 需要一个适配器 代码如下:

public class MainActivity extends AppCompatActivity {
    //声明一个数组 声明的内容就是这个控件要展示的内容
    private static final String[] COUNTRIES = new String[] {
            "老张", "老方", "老黎", "老毕", "老冯","老邱","aaa","aab"
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //1.找到控件
        AutoCompleteTextView actv = (AutoCompleteTextView) findViewById(R.id.actv);
        //2.声明适配器   actv这个控件展示数据和listview一样 也需要适配器 参数2:使用系统提供好的一个布局
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_dropdown_item_1line, COUNTRIES);
        //3.actv和适配器关联
        actv.setAdapter(adapter);
    }
}
ff

2.button和imageButton:

这里写图片描述

3.自定义控件的方式

[1]通过原生的控件进行组合达到自定义的需求
[2]定义一个类继承View
[3]定义一个类继承ViewGroup

4.下拉列表

需求分析 通过edittext button popupwindow listview组合达到自定义需求
代码实现步骤
[1]画UI

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    tools:context="itheima.a2_.MainActivity">

    <EditText
        android:id="@+id/et_number"
        android:layout_width="250dp"
        android:layout_height="wrap_content" />
    <ImageButton
        android:id="@+id/ib_down"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignRight="@id/et_number"
        android:background="@null"
        android:src="@drawable/down_arrow" />
</RelativeLayout>

[2]初始化popupwindow

 //弹出一个popupwindow
    private void showpopUpWindow() {
        //0.准备一个listview 给popupwindow使用
         ListView view = initListView();
        //1.创建一个popupwindow
        // 参数1:View 指定popupwindow具体展示什么样的内容 -->展示listview
        //参数2 3:宽 和 高 popupwindow的宽和高 宽应该和edittext一样宽 高自定义
        PopupWindow pw = new PopupWindow(view,et_number.getWidth(),250,true);
        //2.展示出来 展示到edittext 下面
        pw.showAsDropDown(et_number);
    }

[3]准备popupwindow要展示的内容—>listview 先画listview条目的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:padding="5dp"
    android:layout_height="wrap_content">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/user"
        />
    <TextView
        android:id="@+id/tv_content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="哈哈"
        android:gravity="center_horizontal"
        android:textSize="25sp"
        android:textColor="#000"
        />
    <ImageButton
        android:id="@+id/ib_delete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/delete"
        android:background="@null"
        />
</LinearLayout>

[4]展示listviet的数据

 private ListView initListView() {
        //1.通过打气筒把一个布局转换为一个view对象
        ListView listview = (ListView) View.inflate(getApplicationContext(), R.layout.listviewbg, null);
        //2.给listview指定分割线
        listview.setDivider(new ColorDrawable(Color.GRAY));
        //3.设置分割线高度
        listview.setDividerHeight(1);
        //2.把lists集合里面的数据展示到listview上
        listview.setAdapter(new MyAdapter());
        return listview;
    }
    //定义listview的适配器
    class MyAdapter extends BaseAdapter{
        //listview一共要展示多少个条目
        @Override
        public int getCount() {
            return lists.size();
        }
        @Override
        public Object getItem(int position) {
            return null;
        }
        @Override
        public long getItemId(int position) {
            return 0;
        }
        //获取一view  用来展示listview每个条目的内容
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            //1.对listview优化
            View view;
            if (convertView == null){
                view = View.inflate(getApplicationContext(),R.layout.list_item,null);
            }else{
                view = convertView;
            }
            //2.找到条目的控件
            TextView tv_content = (TextView) view.findViewById(R.id.tv_content);
            ImageButton ib_delete = (ImageButton) view.findViewById(R.id.ib_delete);
            //3.更新数据
            tv_content.setText(lists.get(position));
            //4.给按钮设置点击事件
            ib_delete.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this, "delete", Toast.LENGTH_SHORT).show();
                }
            });
            return view;
        }
    }

[5]点击listview的条目 把点击条目的数据取出来展示到edittext上

listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                //6.把点击条目的数据取出来  数据在哪里存着就取哪里取
                String data = lists.get(position);
                //7.展示到EditText 控件上
                et_number.setText(data);
                //8.关闭popupwindow
                pw.dismiss();
            }
        });

注意:当listview 的条目上有button checkbox这些控件 会抢占条目的事件 —->解决方案在条目的根布局上面加如下属性

android:descendantFocusability="blocksDescendants"

[6]点击删除按钮逻辑
ib_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//6.把对应条目删除 把数据先在集合里面删除
lists.remove(position);
//7.通知适配器更新
notifyDataSetChanged();
}
});

5.view绘制流程

[1]测量
measure方法完成对view测量 –>实际测量工作在onMeasure方法里面实现,因为measure方法是final的 –>最终调用setMeasuredDimension这个方法完成测量工作,当我们想自己对view进行测量的时候,重写onMeasure方法,调用setMeasuredDimension方法就可以了.
[2]排版
底层调用layout —->setFrame方法完成对view摆放.
view在屏幕占据的是一个矩形区域,view的职责是绘制和事件处理.
[3]绘制 draw

         *      1. Draw the background   画背景
         *      2. If necessary, save the canvas' layers to prepare for fading  画图层 跳过
         *      3. Draw view's content   画内容 -->重写onDraw
         *      4. Draw children   画孩子  当继承ViewGropp的时候才涉及画孩子
         *      5. If necessary, draw the fading edges and restore layers  跳过
         *      6. Draw decorations (scrollbars for instance)  画滚动条

[4]绘制实战
4.1)画线

canvas.drawLine(10,20,50,70,paint);

4.2) 画圆

canvas.drawCircle(100,100,30,paint);

4.3)画图片

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.haha);
        canvas.drawBitmap(bitmap,0,0,null);

4.3)画三角形 三个点连接起来

Path path = new Path(); ///创建一个路径 可以把几个点连接起来
        //定义三角形三个点
        int x1=100,y1=0;
        int x2 =0,y2=195;
        int x3=195,y3=195;
        //把上面三个点连接起来
        path.moveTo(x1,y1);
        path.lineTo(x2,y2);
        path.lineTo(x3,y3);
        path.lineTo(x1,y1);
        canvas.drawPath(path,paint);

4.4)画弧
//5.画弧

RectF rf  = new RectF(5,5,195,195);
        canvas.drawArc(rf,0,mProgress,false,paint);

6.开关案例

代码实现步骤
[1]先在构造方法里面获取2张背景图片的宽和高

//这个类在布局中使用
    public ToogleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //1.把准备好的2张图片 转换为bitmap对象
        toogleBg = BitmapFactory.decodeResource(getResources(), R.drawable.toogle_background);
        slideBg = BitmapFactory.decodeResource(getResources(), R.drawable.toogle_slidebg);
    }

[2]重写onMeasure方法对当前view进行测量 当前view的宽高和toogleBg 一样

//对当前的view 自己测量 当前view的宽就是toogleBg的宽  view的高就是toogleBg的高
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //自己测量
        setMeasuredDimension(toogleBg.getWidth(),toogleBg.getHeight());
    }

[3]重写onDraw方法往当前的view上画内容 其实就是画图片

//代表往当前view上画内容--->实际上就是画图片
    @Override
    protected void onDraw(Canvas canvas) {
        //1.画开关背景
        canvas.drawBitmap(toogleBg,0,0,null);
        //2.画滑动块背景
        canvas.drawBitmap(slideBg,0,0,null);
    }

[4]给开关定义监听事件 具体什么时候出发回调事件:

//定义接口回调
    public interface OnToogleListener{
        //回调方法
        void onState(boolean state);
    }
    //定义监听器方法
    public void setOnToogleListener(OnToogleListener listener){
        //接收外面传递过来的实例
        this.mListener = listener;
    }

[5]处理滑动块滑动的逻辑 重写onTouchEvent方法 处理手指按下和移动的逻辑

@Override
    public boolean onTouchEvent(MotionEvent event){
        //1.获取触摸事件类型
        int action = event.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:  //按下
                //1.获取手指按下的坐标
                downX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:  //移动
                //1.获取移动的坐标
                float movex = event.getX();
                //2.算出移动距离
                float distancX = movex - downX;
                //3.对slideLeftPosition 重新赋值
                slideLeftPosition+=distancX;
                //4.对滑动块的边界解析判断
               if (slideLeftPosition <=0){
                    slideLeftPosition = 0;
                }else if(slideLeftPosition >=slideRightMaxPosition){
                   slideLeftPosition = slideRightMaxPosition;
               }
                //5.改变一下起始点坐标
                downX = movex;
                break;
            case MotionEvent.ACTION_UP:  //抬起
                break;
        }
        invalidate();//--->onDraw方法就会执行
        return true; //让当前控件消费事件
    }

[6]处理手指抬起的业务逻辑

case MotionEvent.ACTION_UP:  //抬起
                //1.获取开关背景中心点
                float toogleBgCenter =  toogleBg.getWidth()/2;
                float slideBgCenter =slideLeftPosition + slideBg.getWidth()/2;
                //2.做判断
                if (slideBgCenter <= toogleBgCenter){
                    //说明开关是关闭状态
                    slideLeftPosition = 0;
                }else{
                    slideLeftPosition = slideRightMaxPosition;
                }
                break;

[7]实现开关的功能.

 //3.如果手指抬起了 实现开关 开关的功能
        if (isHandup){
            isHandup = false;
            //4.当手指抬起的时候获取当前开关的状态
            boolean isOpenTemp = slideLeftPosition > 0;
            //5.对开关的状态进行判断 判断开关的状态是否发生了变化
            if (isOpen!=isOpenTemp && mListener!=null){
                //说明开关的状态发生了改变  并且监听器实例不为空  触发接口的回调方法
                mListener.onState(isOpenTemp);
                //6.改变开关默认状态
                isOpen = isOpenTemp;
            }
        }

[8]自定义开关的属性

8.1)在res下定义一个attrs文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="toogleView">
        <attr name="state" format="boolean" />
    </declare-styleable>
</resources>

8.2)自己声明一个命名空间.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:itheima="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main"
    android:layout_width="match_parent" android:layout_height="match_parent"
    tools:context="com.itheima.toogleview.MainActivity">

    <com.itheima.toogleview.ToogleView
        android:id="@+id/toogleview"
        android:layout_width="wrap_content"
        itheima:state="false"
        android:layout_centerInParent="true"
        android:layout_height="wrap_content" />
</RelativeLayout>

8.3)在构造方法里面获取我们声明的属性值

//3.获取布局文件中声明的属性值
        String nameSpace = "http://schemas.android.com/apk/res-auto";
        boolean state = attrs.getAttributeBooleanValue(nameSpace, "state", false);

 8.4)更新开关的状态
//更新开关的状态
    private void setToogleState(boolean state) {
        if (state){
            //说明开关是开的状态
            slideLeftPosition = slideRightMaxPosition;
        }else{
            //说明开关是关的状态
            slideLeftPosition = 0;
        }
    }
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页