|
Android自定义组件
在原生组件上避免不了覆写、组合等,以定义自己的组件,也方便以后复用。例如之前工程里出现了多次的文件浏览器组件。
嗯~,该怎么总结呢?
一、概述
自定义组件,大概可以这么分吧。一、View或SurfaceView上自绘;二、ViewGroup布局子类整合;三、不清楚了~,好像也没什么好分的==。
本文的工程,个人觉着主要还是属性资源的使用吧?工程主要例子介绍如下:
名称 | 效果 | 属性 | Loading动态...的效果组件 | loading...的动态效果 | 定义了如下四属性:
1)loadImage:load字图片,reference类型
2)pointImage:小点图片,reference类型
3)pointCount:小点数目,integer类型
4)msecRate:毫秒级变化速率,integer类型 | Title背景移位的效果组件 | 集合View布局,形成标题栏。实现了标题项下的背景移动的小效果。 | 定义了如下属性:
1)titleLayout:标题栏布局
2)bgImage:item背景图片
3)bgLeftMargin:背景初始左边距
4)animTime:移动动画时间 | ViewPager绑定标题的效果组件 | ViewPager绑定标题栏,并实现了标题项下的背景移动的小效果。 | 效果特征如下:
1)背景随ViewPager滚动而同步在标题间滚动
2)点击标题时,ViewPager程序控制滚动&背景同步
属性定义如下:
1)tLayout:标题栏布局
2)bImage:item背景图片
3)bMargin:背景初始左边距 | ListView增加抽屉的效果组件 | ListView增加抽屉的效果组件。抽屉打开的界面只用了一个。 | 1)listViewId:列表视图id,reference类型
2)drawerContent:抽屉内容视图id,reference类型
3)drawerClose:抽屉内容的关闭按钮id,reference类型 | 自定义能隐藏更多标题的组件 | 集合View布局,形成标题栏。实现超过标题数限制时,自动显示更多的效果。 | 初始化时,需要进行如下步骤:
1)设置显示数限制,默认将为6。
2)绑定标题内容。为String[],将直接以TextView显示==
3)绑定更多操作的视图id。将自己加载,并为其设置点击事件。
4)绑定更多显示的视图。应为已有的ViewGroup。将自动加载超出限制的标题内容(TextView)。更多操作则将控制其显示或隐藏。
另外,提供刷新内容的方法,用于:一、标题栏内容的重新加载;二,更多显示内容的重新加载。 | 自绘实时动态数据线 | 利用View绘制的实时数据显示组件? | 写该文档时才挪进来的了,感觉弄得乱乱的。
双点缩放好像很不正确啊?应该是两个触摸点没弄对,获得的是一个手指头触发的两个点,所以一下放大了。(猜测,总之我是不修了^^) |
以下将以“ViewPager扩展组件”为例了,顺便能看下ViewPager组件^^。
二、步骤
带属性资源,整合布局构建自定义组件的步骤~
1)attrs.xml
定义组件需要用的属性。不用的话,就相当于一个类为一个自定义组件,不和这些东西挂钩。“自定义能隐藏更多标题的组件”即是这样的,连属性都没定义==。
ViewPager扩展组件定义的内容: - <!-- TitleViewPager -->
- < declare-styleable name = "TitleViewPager" >
- < attr format = "reference" name = "tLayout" />
- < attr format = "reference" name = "bImage" />
- < attr format = "integer" name = "bMargin" />
- </ declare-styleable >
format类型,参见:Android中attr自定义属性详解。
2)item.xml
只要是xml的resources标签内即可,单独弄出来呢最好。用以下方式定义一个id,用于View.setId(int id),主要用于相对布局时,相对于某个id的View什么的。
ViewPager扩展组件定义的内容: - < item name = "containerLayout" type = "id" />
3)创建组件
其类中引用属性资源。并看下ViewPager的使用说明吧:OnPageChangeListener接口方法和PagerAdapter适配器内方法的注释。
- public class TitleViewPager extends RelativeLayout implements
- OnPageChangeListener, View.OnClickListener {
- private Context mContext; // 上下文
- private LayoutInflater mInflater; // 布局加载器
- private int titleLayoutId; // 标题栏布局id
- private int bgImageResId; // item背景图片资源id
- private int bgLeftMargin; // 背景初始左边距
- private View titleLayout; // 标题栏布局
- private ImageView mBgImage; // item背景图片
- private ArrayList<View> mItemViews; // 标题项视图集合
- private ViewPager mViewPager; // ViewPager组件
- private ArrayList<View> mPageViews; // 页面视图集合
- private int prevOffset = - 1 ; // 前次偏移值,这里用了int值像素
- private int currentIndex; // 当前页面索引
- private int previousIndex; // 前次页面索引
- private boolean isTitleClicked; // 标题项点击
- private OnPageChangeListener mOnPageChangeListener; // 页面变化监听事件
- // private final int REFRESH_RATE = 20; // 刷新速率20msec
- // private Scroller mScroller; // 滚动器
- // private static final Interpolator sInterpolator = new Interpolator() {
- // public float getInterpolation(float t) {
- // t -= 1.0f;
- // return t * t * t + 1.0f;
- // }
- // };
- public TitleViewPager(Context context, AttributeSet attrs) {
- super (context, attrs);
- mContext = context;
- mInflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- // 获得TypedArray对象
- TypedArray typedArray = context.obtainStyledAttributes(attrs,
- R.styleable.TitleViewPager);
- // 获取标题栏布局id,默认0
- titleLayoutId = typedArray.getResourceId(
- R.styleable.TitleViewPager_tLayout, 0 );
- // 获取item背景图片资源id,默认0
- bgImageResId = typedArray.getResourceId(
- R.styleable.TitleViewPager_bImage, 0 );
- // 获取背景初始左边距,默认0
- bgLeftMargin = typedArray.getInt(R.styleable.TitleViewPager_bMargin, 0 );
- initLayout(); // 初始化标题栏&ViewPager
- mItemViews = new ArrayList<View>();
- mPageViews = new ArrayList<View>();
- }
- // 初始化标题栏&ViewPager
- private void initLayout() {
- RelativeLayout containerLayout = new RelativeLayout(mContext); // 创建标题栏容器布局
- containerLayout.setId(R.id.containerLayout); // 设置标识符
- LayoutParams containerParams = new LayoutParams(
- LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); // 宽度WRAP_CONTENT,高度WRAP_CONTENT
- containerParams.addRule(RelativeLayout.ALIGN_PARENT_TOP,
- RelativeLayout.TRUE); // 贴于顶部
- containerParams.addRule(RelativeLayout.CENTER_HORIZONTAL,
- RelativeLayout.TRUE); // 水平居中
- addView(containerLayout, containerParams); // 当前布局增加容器布局
- if ( 0 != bgImageResId) {
- mBgImage = new ImageView(mContext); // 创建item背景图片
- mBgImage.setImageResource(bgImageResId); // 设置item背景图片
- LayoutParams p_w_picpathParams = new LayoutParams(
- LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); // 宽度WRAP_CONTENT,高度WRAP_CONTENT
- p_w_picpathParams.addRule(RelativeLayout.CENTER_VERTICAL,
- RelativeLayout.TRUE); // 垂直居中
- p_w_picpathParams.leftMargin = bgLeftMargin; // 左边距
- containerLayout.addView(mBgImage, p_w_picpathParams); // 标题栏容器增加标题item背景图片
- }
- if (titleLayoutId != 0 ) {
- titleLayout = mInflater.inflate(titleLayoutId, this , false ); // 获得标题栏布局
- LayoutParams titleParams = new LayoutParams(
- LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); // 宽度WRAP_CONTENT,高度WRAP_CONTENT
- titleParams.addRule(RelativeLayout.CENTER_HORIZONTAL,
- RelativeLayout.TRUE); // 水平居中
- titleParams.addRule(RelativeLayout.CENTER_VERTICAL,
- RelativeLayout.TRUE); // 垂直居中
- containerLayout.addView(titleLayout, titleParams); // 标题栏容器增加标题栏布局
- }
- mViewPager = new ViewPager(mContext); // 创建ViewPager
- mViewPager.setAdapter( new MPagerAdapter()); // 设置ViewPager适配器
- mViewPager.setOnPageChangeListener( this ); // 设置页面改变的监听接口
- LayoutParams viewPagerParams = new LayoutParams(
- LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); // 宽度FILL_PARENT,高度FILL_PARENT
- viewPagerParams.addRule(RelativeLayout.BELOW, containerLayout.getId()); // 布于标题栏容器下方
- viewPagerParams.addRule(RelativeLayout.CENTER_HORIZONTAL,
- RelativeLayout.TRUE); // 水平居中
- addView(mViewPager, viewPagerParams); // 当前布局增加容器ViewPager
- }
- // 增加一个绑定页面
- public void addBindedPage( int pageViewId, int titleItemId) {
- mPageViews.add(mInflater.inflate(pageViewId, this , false ));
- View item = titleLayout.findViewById(titleItemId);
- item.setOnClickListener( this );
- mItemViews.add(item);
- }
- // 获得页面数量
- public int getCount() {
- return mPageViews.size();
- }
- // 初始化页面(需要在UI加载完后,可以覆写onWindowFocusChanged())
- public void setPage( int index) {
- setImagePosition(index); // 设置图像至标题项位置
- mViewPager.setCurrentItem(index, false ); // 设置ViewPager当前页面
- }
- // 设置当前页面
- public void setCurrentPage( int index, boolean isAnim) {
- previousIndex = currentIndex; // 记录前次页面索引
- currentIndex = index; // 设置当前页面索引
- mViewPager.setCurrentItem(index, isAnim); // 设置ViewPager当前页面
- // Title移动绑定在ViewPager的滚动事件内
- }
- // 设置图像至标题项位置
- private void setImagePosition( int index) {
- previousIndex = currentIndex; // 记录前次页面索引
- currentIndex = index; // 设置当前页面索引
- if ( null == mBgImage) {
- return ;
- }
- LayoutParams params = (RelativeLayout.LayoutParams) mBgImage
- .getLayoutParams(); // 获得图片布局
- View item = mItemViews.get(index); // 标题项
- // 注:UI加载完后getLeft()才有值
- int targetLeftMargin = ( int ) (item.getLeft() + item.getWidth() / 2.0 - mBgImage
- .getWidth() / 2.0 ); // 目标左边距
- // 位置未变时直接返回,以避免多次setLayoutParams(...)
- if (params.leftMargin == targetLeftMargin) {
- return ;
- }
- params.leftMargin = targetLeftMargin;
- mBgImage.setLayoutParams(params); // 使设置生效
- }
- // 设置图像移动像素距离
- private void moveImagePosition( int offset) {
- if ( null == mBgImage) {
- return ;
- }
- LayoutParams params = (RelativeLayout.LayoutParams) mBgImage
- .getLayoutParams(); // 获得图片布局
- params.leftMargin += offset;
- mBgImage.setLayoutParams(params); // 使设置生效
- }
- /*
- * 当前页滚动时调用,无论是程序控制的平滑滚动还是用户发起的触摸滚动。
- * arg0:第一个页面当前显示的位置索引。如果页面偏移不是0,下一个页面将会可见。
- * arg1:表示第二个页面位置偏移量的比例值,[0, 1)。(右侧页面所占屏幕百分比)
- * arg2:表示第二个页面位置偏移量的像素值。(右侧页面距右边的像素值)
- */
- @Override
- public void onPageScrolled( int position, float positionOffset,
- int positionOffsetPixels) {
- // 判断是否不在动画,用positionOffsetPixels判断判断原因是:
- // 1)position,在选中了页面时就会改变,自动动画时的不能判断
- // 2)positionOffset,动画完变为0.0,但float不好直接等于判断
- // 3)positionOffsetPixels,动画完变为0,int型^^
- if (positionOffsetPixels == 0 ) {
- setImagePosition(position);
- prevOffset = - 1 ;
- isTitleClicked = false ;
- return ;
- }
- // 刚移动时,记录下该次值
- if (prevOffset == - 1 ) {
- prevOffset = positionOffsetPixels;
- return ;
- }
- int pageOffset = positionOffsetPixels - prevOffset; // 页面偏移距离
- prevOffset = positionOffsetPixels;
- if ( null != mBgImage) {
- try {
- if (pageOffset < 0 ) { // 左->右
- int prevIndex, nextIndex;
- if (isTitleClicked) {
- prevIndex = previousIndex;
- nextIndex = currentIndex;
- } else {
- prevIndex = currentIndex;
- nextIndex = currentIndex - 1 ;
- }
- // 两菜单项间的距离
- int itemDistance = mItemViews.get(prevIndex).getLeft()
- - mItemViews.get(nextIndex).getLeft();
- // 图片偏移距离
- int p_w_picpathOffset = pageOffset * itemDistance
- / mViewPager.getWidth();
- // 设置图像移动像素距离
- moveImagePosition(p_w_picpathOffset);
- } else if (pageOffset > 0 ) { // 右->左
- int prevIndex, nextIndex;
- if (isTitleClicked) {
- prevIndex = previousIndex;
- nextIndex = currentIndex;
- } else {
- prevIndex = currentIndex;
- nextIndex = currentIndex + 1 ;
- }
- // 两菜单项间的距离
- int itemDistance = mItemViews.get(nextIndex).getLeft()
- - mItemViews.get(prevIndex).getLeft();
- // 图片偏移距离
- int p_w_picpathOffset = pageOffset * itemDistance
- / mViewPager.getWidth();
- // 设置图像移动像素距离
- moveImagePosition(p_w_picpathOffset);
- }
- } catch (IndexOutOfBoundsException e) {
- // 类似在中间左右左右的来回拖拽==,判断还不够啊T^T
- setImagePosition(currentIndex);
- isTitleClicked = false ;
- }
- }
- if ( null != mOnPageChangeListener) {
- mOnPageChangeListener.onPageScrolled(position, positionOffset,
- positionOffsetPixels);
- }
- }
- /*
- * 当一个新页面被选中时被调用。动画不一定必须完成。
- * arg0:新选中页面的位置索引
- */
- @Override
- public void onPageSelected( int position) {
- if ( null != mOnPageChangeListener) {
- mOnPageChangeListener.onPageSelected(position);
- }
- }
- /*
- * 滚动状态改变时调用。用于发现用户何时开始拖动、页面何时自动沉降到当前页(用户不拖动时)、或者何时完全停止/空闲。
- * arg0:新的滚动状态。SCROLL_STATE_DRAGGING、SCROLL_STATE_SETTLING、SCROLL_STATE_IDLE
- */
- @Override
- public void onPageScrollStateChanged( int state) {
- if (state == ViewPager.SCROLL_STATE_DRAGGING) { // 用户拖动时
- isTitleClicked = false ;
- }
- if ( null != mOnPageChangeListener) {
- mOnPageChangeListener.onPageScrollStateChanged(state);
- }
- }
- // 自定义的ViewPager适配器
- private class MPagerAdapter extends PagerAdapter {
- /*
- * 移除一个给定位置的页面。适配器有责任从它的容器中移除视图,虽然这仅必须确认动作是在finishUpdate()后按时间完成的。
- * arg0:容器视图,从中将移除页面。
- * arg1:移除的页面位置
- * arg2:和instantiateItem(View, int)返回的一样的对象
- */
- @Override
- public void destroyItem(View arg0, int arg1, Object arg2) {
- ((ViewPager) arg0).removeView(mPageViews.get(arg1));
- }
- /*
- * 当显示的界面完成变化后调用。在这里,你应当必须确保所有的页面已经真正的从容器中增加或删除。
- * arg0:容器视图,用于显示适配器的页面视图
- */
- @Override
- public void finishUpdate(View arg0) {
- }
- // 返回可用界面的数量
- @Override
- public int getCount() {
- return mPageViews.size();
- }
- /*
- * 创建一个给定位置的界面。适配器有责任给这边给出的容器增加一个视图,虽然这仅必须确认动作是在finishUpdate()后按时间完成的。
- * arg0:容器视图,在里面将显示页面。
- * arg1:要被装载的页面位置
- * Object:返回一个展现新画面的对象。这不必须是一个View,也可以是一些其他的页面容器。
- */
- @Override
- public Object instantiateItem(View arg0, int arg1) {
- ((ViewPager) arg0).addView(mPageViews.get(arg1), 0 );
- return mPageViews.get(arg1);
- }
- // 是否是由对象生成的视图
- @Override
- public boolean isViewFromObject(View arg0, Object arg1) {
- return arg0 == (arg1);
- }
- // 恢复状态
- @Override
- public void restoreState(Parcelable arg0, ClassLoader arg1) {
- }
- // 保存状态,返回序列化对象
- @Override
- public Parcelable saveState() {
- return null ;
- }
- /*
- * 当显示的页面将要开始变化时调用
- * arg0:容器视图,用于显示适配器的页面视图
- */
- @Override
- public void startUpdate(View arg0) {
- }
- }
- @Override
- public void onClick(View v) {
- int size = mItemViews.size(); // 大小
- for ( int i = 0 ; i < size; i++) {
- if (mItemViews.get(i).getId() == v.getId()) {
- isTitleClicked = true ;
- setCurrentPage(i, true );
- break ;
- }
- }
- }
- // 获得标题项视图集合
- public ArrayList<View> getItemViews() {
- return mItemViews;
- }
- // 获得 页面视图集合
- public ArrayList<View> getPageViews() {
- return mPageViews;
- }
- // 设置页面变化监听事件
- public void setOnPageChangeListener(OnPageChangeListener listener) {
- mOnPageChangeListener = listener;
- }
- }
|
----------------------------
原文链接:https://blog.51cto.com/vaero/872734
程序猿的技术大观园:www.javathinker.net
[这个贴子最后由 flybird 在 2020-06-05 08:15:29 重新编辑]
|
|