1,自定义控件分类:
1.1组合控件:由安卓中原生的控件组合起来,配合动画达成的效果
1.2自定义控件
1.3组合控件案例演示:
案例:优酷菜单demo
三层圆环,按下menu键会通过动画效果消失在界面,点击小房子和中层圆环,最外层圆环消失
①布局实现:
三层相对布局相互叠加(因为图片背景是透明的,所以可以叠加显示)
由于三个布局是叠加显示的,所以这个菜单选项要使用一个占据焦点比较强的(不然有可能点击不到)ImageButton控件
控件上background=”@android:color/transparent”//透明色
②动画效果,三级菜单的隐藏和显示
动画效果,点击二级菜单,三级菜单旋转消失,再次点击旋转回来显示
每一层实际上是一个正方形(圆环在一个背景透明的正方形上),那么只要它做自身的的旋转,就能起到圆环旋转动画的实现,从界面消失,即以底部居中为中心左旋转180度,从界面出现,即以底部居中为中心右旋转180度,就能实现这个效果.
am.setFillAfter()//设置控件保持在动画结束的状态
am.setStartOffset()//设置延迟执行动画
一级菜单的主要是控制二级菜单,如果三级菜单也存在就一起控制,三级菜单先消失,二级菜单延迟消失.但是再次点击一级菜单并不会返回三级菜单
点击menu按钮,三个菜单全部消失,延迟有序
③额外:动画播放完了,圆环隐藏了,但是还可以被点击,在执行隐藏的时候设置按钮不可用
setEnable()//设置是否可用
动画没播放完就再次点击会终止未执行完的动画,所以,在执行隐藏或显示的动画时,设置一个监听器,如果动画播放完了,才能运行执行下一个动画(定义一个变量控制播放动画的数量)
1.4 ViewPager android3.0版本就已经含有此控件(实现图片轮播效果)
Support-v4.jar包包含了ViewPager控件,低版本就可以使用这个控件了
低版本开发的时候,导入viewPager,拷贝全类名使用该控件(类似自定义控件)
在布局文件中的颜色#66(透明度)66(R)66(G)66(B)
viewPager用法
①根据需求创建imageView的数量,并填充数据,设置图片
②viewPager.setAdapter(PagerAdapter)//设置对应的适配器就行了
如果适配器类参数名异常(argxx)需要导入源码(如果点入类之后找不到导入源码的按钮,就卸载v4包重新add一下,就能导入源码了)
源码路径sdk/extras/android/support/v4/src/java
适配器类中的几个重要方法
getCount()//返回的条目数
isViewFromObject(xx,xx) //判断是否使用缓存,如果返回的是true,使用缓存,不去调用instantiXX方法创建一个新对象,
简单来说:如果用户未滑动出界,该图片对象不改变,是否需要重新初始化一个对象来显示.
返回值推荐写法view==object//如果是一个对象就使用缓存,如果不是就创建一个新的
instantiateItem()//初始化一个条目
预加载功能:会预加载左右两张图片,即初始化左右两张图片
//初始化一个条目,把position对应的imageView添加到ViewPager中
viewPager.addView(imageViewList.get(position));
返回这个view;
destoryItem()//销毁一个条目,position当前被销毁的索引
//移除掉ViewPager中的ImageView
viewPager.removeView(imageViewList.get(position));
额外:如果出现找不到方法,但工程内又确实有jar包,去查配置build path,打钩即可
1.4.2 图片轮播的进度点和图片描述数组的实现
①在循环添加图片的时候,也给进度点对应的父控件添加一个小点,开发中有美工做,这里通过xml.
小圆点的创建
创建shape节点的xml文件
shape属性:shape=”oval” //圆形
子节点corners //弧度5dp即可
Solid //固定颜色
//记得要创建两个,一个带焦点,一个不带焦点的
②添加的时候,记得设置对应参数 设置宽高
View v = new (this)
v.setBacKGxxxxx(id);
LayoutParams params = new xxxx(w,h)//设置宽高
Params.xxxx//可以设置对应的参数,控件间的距离,设置是否被选中(设置一个状态选择器可以实现切换进度小圆点的效果)
v.setLayoutParams(params);//设置参数,必须要设置参数,否则无法显示
//最后添加进Linerlayout中
③图片描述信息的数据索引要匹配上适配器的图片数据
额外:设置默认的选中点,注意代码的顺序,要在设置完数据之后设置.
④根据viewPager切换状态来更改进度点状态和信息显示
viewPager.setOnPageChangeListener(this)//设置页面切换监听器
两个方法
onPageSelected()//当页面被选中(完全显示)的时候,触发此方法
更改进度点父节点的子节点集合中子孩子(进度点的状态)
更改文描述文本的显示
别忘了修改前一个节点的状态(拿个引用记录它,如果position直接+-的话,容易逻辑混乱)
onPageScrolled()//当页面卷曲的时候调用
1.4.3 伪无限循环和动态切换
①viewPager移动到第一个(0)或者最后一个的时候(最大索引),
就不会预加载上一个(-1)或 下一个(最大索引+1)了
所以,让返回的count很大就行(比如2g-1(Integer.Max_Value))(数学小技巧)
那么对应的索引就是count%集合长度//相当于倍数取余,可以对应到指定的索引
额外:初始化的时候,把默认选中的点为返回的count中间的值(这样↔滑动都不会出现停滞阻塞的情况了),要让选中的点在第一个位置,就让中间值再减去倍数取余
pageView.setCurrentItem()//设置默认选中的坐标
额外:设置默认选中点,实际上是调用了onPageSelected()方法,记得调换前一个点和当前点设置的位置,因为一开始这样两个都是0,所以要在后面要设置为选中.在前面的设置为未选中.
②动态切换
开启一个子线程,定期切换viewPager的图片(这是一个修改ui的操作).
但要注意的是,子线程在activity中不会被直接销毁,activity的ondestory()方法中设置一个变量去控制这个无限循环
额外:休眠的操作放到循环最后,就不会多输出一次(因为变量控制的时候,它如果已经睡眠了,还会多输出一次,所以这也算一个小优化)
2 组合控件:下拉选择框(ListView填充),参考效果:
2.1参考布局:
点击向下的箭头,出现下拉框,点击下拉框选项会把对应的号码填充到输入框中
下拉框实际上是popupWindow,内容是一个ListView
设置一个适配器,填充数据
把listView添加到popupWindow上面
PopupWindow pw = new PopupWindow(listview,w,h);
W:宽度:输入框的宽度,et.getWidth();
H;高度:整个屏幕都可以,设死也可以
//显示在界面上,显示在某个控件下
pw.showAsDropDown(输入框,偏移量x,偏移量y)
2.2 细节问题
①外形边框是listView的背景图片
②listView.setVerticalScrollBarEnable()//设置是否显示右侧垂直滑动条
如果全部条目都被删除了,就应该关闭popup.
③Button,imagebutton,checkbox这一类控件抢焦点能力都很强.
PopupWindow本身是不可以使用焦点的,设置可以使用焦点,不生效
原因:子条目的button或imageButton把焦点抢走了
//设置子控件不可以抢占父元素的事件,但是可以以块去分配事件
解决:给子条目LinearLayout布局:descendantFocusablity=”blocksDescXXX”
④popupWindow跟输入框之间有细微的空白
因为输入框本身是有边框宽度的,而获取的输入框宽度不包含它,所以稍微减少一点就行.
⑤点击其它地方也要让它会被关闭
popupWindow.setOutsideTouchable()//点击外部可以被关闭,单独设置不生效
popupWindow.setBackground(xxxx)//设置一个背景,才能让上面的设置生效
3.自定义控件(前面都是组合控件)
3.1 滑动开关,参考效果图,拖动方块可以实现开关切换
①安卓中每一个控件或者布局都是继承自View类(控件的爸爸),创建一个类去继承View
需要重写的构造方法
如果要在java中new出来,就重写只有Context 的构造
如果要在 布局文件中引用自定义控件时,使用有Context和atts的构造
一般两个都写.
②拷贝对应的图片
创建方法(都是公开,调用者可自定义,从这一角度考虑自定义控件功能的实现),
设置背景图片setSwitchBackgorundResource()//这个名字更容易理解,
设置滑动块的图片
设置当前开关的默认状态.
用对应的成员变量去记录下来,方便在绘制的时候处理.
③自定义控件的绘制
android中View的绘制流程
不要直接去覆盖,使用谷歌提供的回调接口
Measure(测量宽高信息) >>>Layout(排版/布局,包含子控件) >>>draw(绘制)
onMeasure(当测量时调用)>>>onLayout(布局时回调) >>>onDraw(当绘制时)
3.2 绘制控件
①在onMeasure方法中测量并设置自己的宽和高
重写onMeasure(xxx)方法
设置宽高和背景图片的宽和高一致
setMeasureDimension(w,h);//设置测量后的宽高
super.xxx要在最前面,不然不能生效
②没有子控件,不需要onLayout回调,在onDraw()方法中,把开关绘制出来
重写onDraw(Canvas)方法,super.onDraw()//可以删除掉
Canvas 画板,使用canvas所画出来的东西,都是作为当前控件,在屏幕上显示
//把背景图片平铺在当前控件上
Canvas.drawBitMap(图片,左,上,画笔)//0,0(左上角),null(绘制图片不需要画笔)
//根据当前状态currentState(成员变量)来绘制滑动块状态
如果为true,绘制在背景图片右边(左边距为背景宽减去开关宽,上边距为顶部0即可)
如果为false,绘制在背景图片的左边(左边距为0,上边距起点0即可)
3.3触摸移动改变状态
①重写onTouchEvent()//返回值为true消耗当前事件,自己处理,返回为false交给别人处理.
②判断事件类型
观察可知,移动的时候只改变X 轴值
记录按下的点,移动时候的点,两者的x轴值相减就是位移范围(正数向左,负数向右).
如果采用这种办法,需要记录下初始的位置,再对其进行修改
实际上,直接用移动的点数值给左边距设置数值即可.
当移动的点发生改变的时候,手动触发onDraw方法的重绘.
invaliadte()//刷新当前控件,会引起onDraw方法的调用.
③在onDraw()方法里,根据X轴的值来动态的修改滑动块的位置
Canvas.drawBitMap(滑动块图片,左边距,0上边距不变,null不需要画笔);
3.3.2用户体验问题
①会移出边界
限定宽度,滑动块的左边距不能小于0,如果小于0,绘制的时候就一直设置为0
左边距(滑动块的左边距)同样也不能大于(背景图片的宽度-滑动块的宽度),
如果大于,就一直为两者之差.
②移动的时候,如果在中间松开,不会自动归位
先判断是否在滑动中(不然滑动的时候就会直接跳位,体验不好)
松开的时候(设置为不在滑动中),根据左边距的值判断状态
如果不在滑动中,两种方法
(判断滑动块的左边距是否大于整个背景图片的四分之一,
如果大于就代表用户想要关闭它,如果小于就代表用户想要开启它.根据结果跳位)
(或者滑动块的中心点和背景图片的中心点值的比较,
如果滑动块中心点>背景图片中心点值,当前状态重置为true,打开的状态
如果小于,就重置为 false,关闭的状态
)
③手指按下位置与用户预期不一样(用户希望的是移动的点是中间的点,而实际上是按下的点)
重新绘制的时候,左边距的数值>>调整>>左边距减去滑动块宽度的一半.
最后再对这个数据进行判断.