AndroidUI之如何自定义控件
来源:互联网 发布:北电网络 编辑:程序博客网 时间:2024/06/05 22:58
前言
对Android开发者而言,学习到一定时候,就会发现Android自带的控件诸如TextView,Button,ImageView,配合上布局如LinearLayout, RelativeLayout,TableLayout,FrameLayout等,做不出自己想要的效果。这个时候就要用到自定义控件。
- 自定义空间的有点有很多,包括:
- 用户交互体验的优化;
- 在大数据量情况下自定义控件比写布局效率高
- 布局上更加符合需求;
- 能配合App整体UI风格的设计;
- 等等
现在网上有许多开源的自定义控件共享,供开发者使用。而我们在实际情况中也会遇到需要自定义控件的时候,今天我们就来一起学习如何自定义控件。
(PS:本文内容基于官方教程进行学习解析,若想查看原文请点击阅读。
参考博客:guolin博文&博文,大家可以看看,受益匪浅)
View
View是我们平时所见到的所有视图的父类,每个布局,控件,都是直接或者间接继承自View。一个View在屏幕上显示出来要涉及到三个函数measure(),layout()和draw()。
它们的作用分别是:
- measure()测量视图布局大小
- layout()设置视图在屏幕中的位置
- draw()将视图显示在屏幕上
Measure:
- 调用measure()测量
- measure()中调用onMeasure(),onMeasure()是实际测量视图大小的函数
- onMeasure()中调用setMeasureDimension()保存测量结果;
Layout:
- layout调用setFrame(l,t,r,b),l,t,r,b即left, top, right, bottom,是与父View的距离。setFrame()会判断这个视图大小有没有改变来决定是否重绘。然后调用onLayout()
- onLayout()是个空方法由子类去实现,如TextView继承自View里面就对这个方法进行了重写。
- 特别一提,onLayout()在ViewGroup中是个抽象方法,没有进行实现。意思是在如果你创建了一个View继承自ViewGroup,则必须重写onLayout()方法。
Draw:
- draw会在一个canvas对象上进行绘制,然后显示在屏幕上
- 和上面两个类似,draw()中也有实际上负责绘制逻辑的函数onDraw(),诸如TextView,ImageView中有自己重写的onDraw()函数。
- onDraw()通过对传入的canvas对象进行逻辑处理,得到所要显示的样子。
自定义控件
知道了View的绘制流程之后,我们开始学习怎么去自定义一个控件。
- 自定义控件按照对View及其子类的依赖程度,可以分为:
- 组合型控件。利用现有控件的组合实现更加复杂的布局。
- 继承型控件。继承现有的View或者View的子类,如TextView,ImageView等,并在此基础上增加一点内容。
- 自创型控件。利用上面说的View绘制原理,自行设计绘制出来的全部内容并加以实现。
组合型控件:
先是在layout中创建一个布局文件,如例子中是一个Button和一个TextView,view_layout.xml,实现类似标题栏的效果:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp" android:background="#ff0000" > <Button android:background="#00ff00" android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5dp" android:textSize="18sp" android:text="Button" android:textColor="#0000ff" /> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="TextView" android:textColor="#0000ff" android:textSize="22sp" /></RelativeLayout>
然后是控件对应的类,在里面实现逻辑处理,CombView.java:
import android.content.Context;import android.util.AttributeSet;import android.view.LayoutInflater;import android.view.View;import android.widget.Button;import android.widget.RelativeLayout;import android.widget.TextView;import com.example.wujiayi.testapplication.R;public class CombView extends RelativeLayout{ private TextView textView; private Button button; private int count; public CombView(Context context, AttributeSet attrs) { super(context, attrs); //加载视图的布局 LayoutInflater.from(context).inflate(R.layout.view_layout, this); textView = findViewById(R.id.text); button = findViewById(R.id.button); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { textView.setText(Integer.toString(++count)); } }); }}
例子比较简单,我就不多做介绍了。在写完布局文件和逻辑实现之后,就能开始使用了,activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.example.testapplication.views.CombView android:layout_width="match_parent" android:layout_height="60dp"/></LinearLayout>
最后得到的效果图如下:
在点击后TextView字样会变成数字并从1开始计数:
继承型控件:
继承型控件我们要做的是一个MyListview,滑动时在上面显示删除按钮,点击删除按钮时删除该条目。老规矩先创建一个布局文件,delete_btn.xml:
<?xml version="1.0" encoding="utf-8"?><Button xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/delete_button" android:text="删除" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
然后是自定义的View的代码,监听手势,逻辑如下所示,也比较简单就不进行解释了,MyListView.java:
//继承自ListView,同时实现OnTouchListener和OnGestureListener接口public class MyListView extends ListView implements View.OnTouchListener, GestureDetector.OnGestureListener { private View delete_btn; private ViewGroup listView; private int seletedItem; private boolean isDeleteshown; private GestureDetector gestureDetector; private OnDeleteListener listener; public MyListView(Context context, AttributeSet attrs) { super(context, attrs); gestureDetector = new GestureDetector(getContext(), this); setOnTouchListener(this); } public void setOnDeleteListener(OnDeleteListener _listener) { listener = _listener; } @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (isDeleteshown) { listView.removeView(delete_btn); delete_btn = null; isDeleteshown = false; return false; }else { return gestureDetector.onTouchEvent(motionEvent); } } @Override public boolean onDown(MotionEvent motionEvent) { if (!isDeleteshown) { seletedItem = pointToPosition((int) motionEvent.getX(), (int) motionEvent.getY()); } return false; } @Override public void onShowPress(MotionEvent motionEvent) { } @Override public boolean onSingleTapUp(MotionEvent motionEvent) { return false; } @Override public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) { return false; } @Override public void onLongPress(MotionEvent motionEvent) { } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (!isDeleteshown && Math.abs(velocityX) > Math.abs(velocityY)) { delete_btn = LayoutInflater.from(getContext()).inflate( R.layout.delete_btn, null); delete_btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { listView.removeView(delete_btn); delete_btn = null; isDeleteshown = false; listener.onDelete(seletedItem); } }); listView = (ViewGroup) getChildAt(seletedItem - getFirstVisiblePosition()); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); params.addRule(RelativeLayout.CENTER_VERTICAL); listView.addView(delete_btn, params); isDeleteshown = true; } return false; } public interface OnDeleteListener { void onDelete(int index); }
到此为止,我们继承自ListVeiw的自定义控件就完成了。下面是使用这个自定义控件的示例,先是创建一个ListView的子项布局文件listview_item.xml:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:descendantFocusability="blocksDescendants" android:orientation="vertical" > <TextView android:id="@+id/listview" android:layout_width="wrap_content" android:layout_height="50dp" android:layout_centerVertical="true" android:gravity="start|center_vertical" android:textColor="#000" /></RelativeLayout>
布局完之后,用一个adapter去加载刚刚写的的布局,MyAdapter.java:
public class MyAdapter extends ArrayAdapter<String>{ public MyAdapter(Context context, int textViewResourceId, List<String> objects) { super(context, textViewResourceId, objects); } @NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { View view; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(R.layout.listview_item, null); } else{ view = convertView; } TextView textView = view.findViewById(R.id.listview); textView.setText(getItem(position)); return view; }}
子项布局和加载都完成了以后,就可以在activity_main.xml中引入这个布局了:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"> <com.example.testapplication.views.MyListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/my_listview"/></LinearLayout>
布局完成,在MainActivity.java中进行点击删除按钮的逻辑实现:
public class MainActivity extends AppCompatActivity { private MyListView listView; private MyAdapter adapter; private List<String> contentList = new ArrayList<String>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initList(); listView = (MyListView) findViewById(R.id.my_listview); listView.setOnDeleteListener(new MyListView.OnDeleteListener() { @Override public void onDelete(int index) { contentList.remove(index); adapter.notifyDataSetChanged(); } }); adapter = new MyAdapter(this, 0, contentList); listView.setAdapter(adapter); } private void initList() { for (int i = 0; i < 10; i++) { contentList.add("Item" + Integer.toString(i)); } }}
一起来看看效果吧:
子项中滑动,出现删除按钮
点击删除后
自创型控件:
自创型控件我们做的是一个圆,类似于统计图中的饼状图。由于是自绘的,所以不存在已有控件布局问题,我们直接从measure,layout,draw三步入手,下面给出TestView.java:
public class TestView extends View { private Paint mPaint; private RectF oval; public TestView(Context context) { super(context); initView(); } public TestView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public TestView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } private void initView(){ mPaint = new Paint(); mPaint.setAntiAlias(true); oval=new RectF(); } @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); switch (widthMode) { case MeasureSpec.EXACTLY: break; case MeasureSpec.AT_MOST: break; case MeasureSpec.UNSPECIFIED: break; } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setColor(Color.GRAY); // FILL填充, STROKE描边,FILL_AND_STROKE填充和描边 mPaint.setStyle(Paint.Style.FILL_AND_STROKE); int with = getWidth(); int height = getHeight(); float radius = with / 4; canvas.drawCircle(with / 2, with / 2, radius, mPaint); mPaint.setColor(Color.BLUE); oval.set(with / 2 - radius, with / 2 - radius, with / 2 + radius, with / 2 + radius);//用于定义的圆弧的形状和大小的界限 canvas.drawArc(oval, 270, 120, true, mPaint); //根据进度画圆弧 }}
主要实现的步骤是在canvas上画了一个圆形,需要重写onDraw()函数。有时候会需要重写onMeasure()和onLayout(),视实际情况而定。
在写完一个自创型控件之后,我们需要在布局中引用,activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"> <com.example.testapplication.views.TestView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" /></LinearLayout>
我们一起来看看效果图:
总结
好了,这篇文章就先写到这里。我们来回顾一下全文内容:
我们首先分析了View在屏幕上显示出来需要的步骤包括measure,layout和draw。
然后一起学习了三种类型的控件,包括组合型,继承型和自创型,并在其中运用了上面所学的View显示的知识。
最后,自定义控件中还要考虑自定义属性的问题,我们会在以后的博文中再一起学习这一点。
文章是自己的一点浅陋的理解,如有不当之处欢迎指出,谢谢观看。
- AndroidUI之如何自定义控件
- AndroidUI之控件宝典
- AndroidUI控件的自定义属性
- androidUI控件
- AndroidUI控件
- AndroidUI控件之TextView及其子类
- AndroidUI控件之imageview及其子类
- androidUI开发之自定义Android标题栏TitleBar布局
- [AndroidUI]自定义view(一)
- AndroidUI组件之RoomButton
- AndroidUI组件之GridView
- AndroidUI组件之TabHost
- AndroidUI组件之Tabhost
- AndroidUI组件之ProgressBar
- AndroidUI组件之ImageSwitcher
- AndroidUI组件之TextSwitcher
- AndroidUI组件之AdapterViewFilpper
- AndroidUI组件之AlertDialog
- 类和对象-第五天
- 视频播放添加模糊背景
- 利用LR做性能测试中出现的常见问题解决方案
- 设计模式_记设计模式研究的开始(0)
- Maven3路程(三)用Maven创建第一个web项目(1)
- AndroidUI之如何自定义控件
- XP安装序列号
- jqgrid加载本地数据分页
- android GridView 在TV上,上下翻页的时候平滑滑动的实现
- Storm的调度器
- PHP实现一个简易的分页类
- FTPrep, 6 Zigzag conversion
- 免费领取全新30套训练数据集, 包含:“股票数据”、“行人检测常用数据”、“汽车数据集” 点击链接,立即领取: http://mp.weixin.qq.com/s/rm_SBbGSVtrmhJcGUp
- Spring MVC使用ModelAndView进行重定向