android自定义控件
为什么我们需要自定义控件呢?
- Android自身带的控件不能满足需求, 需要根据自己的需求定义控件.
- 很多时候没有我们需要使用的控件,或者控件并不美观。
Android本身提供了很多控件比如我们常用的有
文本控件TextView和EditText;
按钮控件Button和ImageButton
状态开关按钮ToggleButton
单选复选按钮RadioButton和RadioGroup
单选按钮和复选按钮CheckBox
图片控件ImageView
时钟控件AnalogClock和DigitalClock
进度条ProgressBar和日期与时间选择控件DatePicker和TimePicker等。
Android本身提供的基本控件
1 文本控件TextView 和EditText
- TextView 控件继承自 View 类。TextView控件的功能是向用户显示文本内容同时可选择性让用户编辑文本。其中TextView不允许编辑。
EditText控件 EditText 控件继承自 TextView。EditText与TextView 最大的不同是 EditText是可以编辑的
2 按钮控件Button 和 ImageButton
- Button控件继承自 TextView 类Button 的用法比较简单主要是为 Button 控件设置 View.OnClickListener.监听器并在监听器的实现代码中编写按钮按下事件的处理代码。
ImageButton 控件 ImageButton 继承自 ImageView。ImageButton 与Button最大的区别是ImageButton没有text 属性既按钮中将显示图片而不是文本。 ImageButton 控件中设置显示图片可以通过android:src 属性也可以通过setImageResurce(int ) 方法来实现
3 状态开关按钮ToggleButton
- CheckBox 和RadioButton 都只有选中和未选中两种状态,可以通过checked属性来设置.
- 不同的是RadioButton 是单选按钮,需要编制到一个RadioGroup中同一时刻一个RadioGroup中只能有一个按钮处于选中状态.
CheckBox和RadioButton 都是继承自 CompoundButton 中继承了一些成员.
4 图片控件ImageView
ImageView 控件负责显示图片,其图片来源既可以是资源文件的id,也可以是Drawable对象或 Bitmap 对象,还可以是 Content Provider 的Uri.
5 时钟控件AnalogClock 和 DigitalClock
- AnalogClock继承自 ViewAnalogClock 控件显示模拟时钟只显示时针和分针
DigeitalClock 继承自 TextView。DigetalClock 显示数字时钟可精确到秒。 时钟控件比较简单只需要在布局文件中声明控件即可。
6 日期与时间选择控件DatePicker 和 TimePicker
- DatePicker 继承自FrameLayout类日期选择控件的主要功能是向用户提供包含年、月、日的日期数据并允许用户对其进行选择。如果要捕获用户修改日期选择控件中数据的事件需要为DatePicker 添加 onDateChangedListener 监听器。
TimePicker 同样继承自FrameLayout 类。时间选择控件向用户显示一天中的时间可以为24小时制可以为AM/PM 制并允许用户进行选择。如果要捕获用户修改时间数据的事件便需要为TimePicker 添加OnTimeChangedListener 监听器
怎么自定义空间呢?
原理:
继承已有控件实现自定义控件
- 通过对android本身提供的控件的代码进行研究,android中控件都是继承view类来实现,通过重写ondraw方法来绘制我们所需要的控件.通过这个我们得到两点提示:
- 我们可以在已有的控件的基础上,通过重写相关方法来实现我们的需求.
- 继承view类或viewgroup类,来绘制我们所需要的控件.一般来讲,通过继承已有的控件,来自定义控件要简单一点.
- 组合已有控件实现自定义控件
- 完全自定义控件实现需求
- 继承已有控件实现自定义控件
自定义控件可以分为三大类型
1. 组合已有的控件实现
- 优酷菜单
- 在xml布局里摆放好.
- 给指定控件添加点击事件.
- 根据业务逻辑,执行动画(旋转动画: 补间动画).
- 菜单按钮的截获.
- 轮播图广告
- 让图片滑动起来(ViewPager).
- 让图片和文字,指示器对应起来.
- 让轮播器无限循环
- 轮播器自动轮询
向右无限循环
0 -> 4 newPosition = position % 5
5 -> 0
6 -> 1
7 -> 2
8 -> 3
9 -> 4
10 -> 0
向左无限循环
设置到中间某个位置.
- 下拉选择框
- Button或ImageButton等自带按钮功能的控件会抢夺所在Layout的焦点.导致其他区域点击不生效.在所在layout声明一个属性
android:descendantFocusability="blocksDescendants"
- popupwindow获取焦点, 外部可点击
// 设置点击外部区域, 自动隐藏
popupWindow.setOutsideTouchable(true); // 外部可触摸
popupWindow.setBackgroundDrawable(new BitmapDrawable()); // 设置空的背景, 响应点击事件
popupWindow.setFocusable(true); //设置可获取焦点
ListView初始化
PopupWindow初始化
2. 完全自定义控件.(继承View, ViewGroup)
- 自定义开关 (View)
- 写个类继承View, OK
- 拷贝包含包名的全路径到xml中, OK
- 界面中找到该控件, 设置初始信息, OK
- 根据需求绘制界面内容,OK
- 响应用户的触摸事件,OK
- 创建一个状态更新监听.OK
- 自定义属性.OK
- 侧滑面板(ViewGroup)
- 在xml布局里摆放内容.
<TextView
style="@style/style_bt_text"
android:drawableLeft="@drawable/tab_news"
android:text="新闻" />
<TextView
style="@style/style_bt_text"
android:drawableLeft="@drawable/tab_read"
android:text="订阅" />
<TextView
style="@style/style_bt_text"
android:drawableLeft="@drawable/tab_local"
android:text="本地" />
<TextView
style="@style/style_bt_text"
android:drawableLeft="@drawable/tab_ties"
android:text="跟帖" />
<TextView
style="@style/style_bt_text"
android:drawableLeft="@drawable/tab_pics"
android:text="图片" />
<TextView
style="@style/style_bt_text"
android:drawableLeft="@drawable/tab_ugc"
android:text="话题" />
<TextView
style="@style/style_bt_text"
android:drawableLeft="@drawable/tab_vote"
android:text="投票" />
<TextView
style="@style/style_bt_text"
android:drawableLeft="@drawable/tab_focus"
android:text="聚合阅读" />
</LinearLayout>
</ScrollView>
- 在自定义ViewGroup里, 进行measure测量, layout布局
- 响应用户的触摸事件
- int scrollX = (int) (downX - moveX);
- getScrollX()获取当前滚动到的位置
- 平滑动画
//1. 开始模拟数据
scroller.startScroll(startX, 0, dx, 0, duration);
invalidate();// 重绘界面 -> drawChild() -> computeScroll();
//2. 在computeScroll中不断获取模拟的数值
@Override
public void computeScroll() {
super.computeScroll();
if(scroller.computeScrollOffset()){
// true, 动画还没有结束
// 获取当前模拟的数据, 也就是要滚动到的位置
int currX = scroller.getCurrX();
scrollTo(currX, 0); // 滚过去
invalidate(); // 重绘界面
}
}
3. 继承已有的控件实现(扩展已有的功能)
- 包含下拉刷新功能的ListView
- 添加了自定义的头布局
<FrameLayout
android:layout_margin="5dp"
android:layout_width="50dp"
android:layout_height="50dp" >
<ImageView
android:id="@+id/iv_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/common_listview_headview_red_arrow" />
<ProgressBar
android:id="@+id/pb"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:indeterminateDrawable="@drawable/shape_progress"
android:visibility="invisible" />
</FrameLayout>
- 默认让头布局隐藏setPadding.设置 -自身的高度
- ListView下拉的时候, 修改paddingTop, 让头布局显示出来
- 触摸动态修改头布局, 根据paddingTop.
- paddingTop = 0 完全显示
- paddingTop < 不完全显示 -64(自身高度)完全隐藏
- paddingTop > 0 顶部空白
- 松手之后根据当前的paddingTop决定是否执行刷新
- paddingTop < 0 不完全显示, 恢复
- paddingTop >= 0 完全显示, 执行正在刷新...
/**
* 根据状态更新头布局内容
*/
private void updateHeader() {
switch (currentState) {
case PULL_TO_REFRESH: // 切换回下拉刷新
// 做动画, 改标题
mArrowView.startAnimation(rotateDownAnim);
mTitleText.setText("下拉刷新");
break;
case RELEASE_REFRESH: // 切换成释放刷新
// 做动画, 改标题
mArrowView.startAnimation(rotateUpAnim);
mTitleText.setText("释放刷新");
break;
case REFRESHING: // 刷新中...
mArrowView.clearAnimation();
mArrowView.setVisibility(View.INVISIBLE);
pb.setVisibility(View.VISIBLE);
mTitleText.setText("正在刷新中...");
if(mListener != null){
mListener.onRefresh(); // 通知调用者, 让其到网络加载更多数据.
}
break;
default:
break;
}
}