天天看点

Android自定义组件(一)

 <b>Android</b><b>自定义组件(一)</b>

         在原生组件上避免不了覆写、组合等,以定义自己的组件,也方便以后复用。例如之前工程里出现了多次的文件浏览器组件。

         嗯~,该怎么总结呢?

<b>一、概述</b>

         自定义组件,大概可以这么分吧。一、View或SurfaceView上自绘;二、ViewGroup布局子类整合;三、不清楚了~,好像也没什么好分的==。

         本文的工程,个人觉着主要还是属性资源的使用吧?工程主要例子介绍如下:

<b>名称</b>

<b>效果</b>

<b>属性</b>

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程序控制滚动&amp;背景同步

属性定义如下:

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组件^^。

<b>二、步骤</b>

         带属性资源,整合布局构建自定义组件的步骤~

<b>1</b><b>)attrs.xml</b>

         定义组件需要用的属性。不用的话,就相当于一个类为一个自定义组件,不和这些东西挂钩。“自定义能隐藏更多标题的组件”即是这样的,连属性都没定义==。

ViewPager扩展组件定义的内容:

&lt;!-- TitleViewPager --&gt; 

&lt;declare-styleable name="TitleViewPager"&gt; 

    &lt;attr format="reference" name="tLayout" /&gt; 

    &lt;attr format="reference" name="bImage" /&gt; 

    &lt;attr format="integer" name="bMargin" /&gt; 

&lt;/declare-styleable&gt; 

<b>2</b><b>)item.xml</b>

         只要是xml的resources标签内即可,单独弄出来呢最好。用以下方式定义一个id,用于View.setId(int id),主要用于相对布局时,相对于某个id的View什么的。

&lt;item name="containerLayout" type="id"/&gt; 

<b>3</b><b>)创建组件</b>

         其类中引用属性资源。并看下ViewPager的使用说明吧:OnPageChangeListener接口方法和PagerAdapter适配器内方法的注释。<b></b>

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&lt;View&gt; mItemViews; // 标题项视图集合 

    private ViewPager mViewPager; // ViewPager组件 

    private ArrayList&lt;View&gt; 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(); // 初始化标题栏&amp;ViewPager 

        mItemViews = new ArrayList&lt;View&gt;(); 

        mPageViews = new ArrayList&lt;View&gt;(); 

    } 

    // 初始化标题栏&amp;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 imageParams = new LayoutParams( 

                    LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); // 宽度WRAP_CONTENT,高度WRAP_CONTENT 

            imageParams.addRule(RelativeLayout.CENTER_VERTICAL, 

                    RelativeLayout.TRUE); // 垂直居中 

            imageParams.leftMargin = bgLeftMargin; // 左边距 

            containerLayout.addView(mBgImage, imageParams); // 标题栏容器增加标题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, 

            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, 

        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) { 

        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) { 

        params.leftMargin = targetLeftMargin; 

        mBgImage.setLayoutParams(params); // 使设置生效 

    // 设置图像移动像素距离 

    private void moveImagePosition(int offset) { 

        params.leftMargin += offset; 

    /* 

     * 当前页滚动时调用,无论是程序控制的平滑滚动还是用户发起的触摸滚动。 

     * 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; 

        // 刚移动时,记录下该次值 

        if (prevOffset == -1) { 

            prevOffset = positionOffsetPixels; 

        int pageOffset = positionOffsetPixels - prevOffset; // 页面偏移距离 

        prevOffset = positionOffsetPixels; 

        if (null != mBgImage) { 

            try { 

                if (pageOffset &lt; 0) { // 左-&gt;右 

                    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 imageOffset = pageOffset * itemDistance 

                            / mViewPager.getWidth(); 

                    // 设置图像移动像素距离 

                    moveImagePosition(imageOffset); 

                } else if (pageOffset &gt; 0) { // 右-&gt;左 

                        nextIndex = currentIndex + 1; 

                    int itemDistance = mItemViews.get(nextIndex).getLeft() 

                            - mItemViews.get(prevIndex).getLeft(); 

                } 

            } catch (IndexOutOfBoundsException e) { 

                // 类似在中间左右左右的来回拖拽==,判断还不够啊T^T 

                setImagePosition(currentIndex); 

                isTitleClicked = false; 

            } 

        if (null != mOnPageChangeListener) { 

            mOnPageChangeListener.onPageScrolled(position, positionOffset, 

                    positionOffsetPixels); 

     * 当一个新页面被选中时被调用。动画不一定必须完成。 

     * arg0:新选中页面的位置索引 

    public void onPageSelected(int position) { 

            mOnPageChangeListener.onPageSelected(position); 

     * 滚动状态改变时调用。用于发现用户何时开始拖动、页面何时自动沉降到当前页(用户不拖动时)、或者何时完全停止/空闲。 

     * arg0:新的滚动状态。SCROLL_STATE_DRAGGING、SCROLL_STATE_SETTLING、SCROLL_STATE_IDLE 

    public void onPageScrollStateChanged(int state) { 

        if (state == ViewPager.SCROLL_STATE_DRAGGING) { // 用户拖动时 

            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:容器视图,用于显示适配器的页面视图 

        public void finishUpdate(View arg0) { 

        // 返回可用界面的数量 

        public int getCount() { 

            return mPageViews.size(); 

         * 创建一个给定位置的界面。适配器有责任给这边给出的容器增加一个视图,虽然这仅必须确认动作是在finishUpdate()后按时间完成的。 

         * arg0:容器视图,在里面将显示页面。 

         * arg1:要被装载的页面位置 

         * Object:返回一个展现新画面的对象。这不必须是一个View,也可以是一些其他的页面容器。 

        public Object instantiateItem(View arg0, int arg1) { 

            ((ViewPager) arg0).addView(mPageViews.get(arg1), 0); 

            return mPageViews.get(arg1); 

        // 是否是由对象生成的视图 

        public boolean isViewFromObject(View arg0, Object arg1) { 

            return arg0 == (arg1); 

        // 恢复状态 

        public void restoreState(Parcelable arg0, ClassLoader arg1) { 

        // 保存状态,返回序列化对象 

        public Parcelable saveState() { 

            return null; 

         * 当显示的页面将要开始变化时调用 

        public void startUpdate(View arg0) { 

    public void onClick(View v) { 

        int size = mItemViews.size(); // 大小 

        for (int i = 0; i &lt; size; i++) { 

            if (mItemViews.get(i).getId() == v.getId()) { 

                isTitleClicked = true; 

                setCurrentPage(i, true); 

                break; 

    // 获得标题项视图集合 

    public ArrayList&lt;View&gt; getItemViews() { 

        return mItemViews; 

    // 获得 页面视图集合 

    public ArrayList&lt;View&gt; getPageViews() { 

        return mPageViews; 

    // 设置页面变化监听事件 

    public void setOnPageChangeListener(OnPageChangeListener listener) { 

        mOnPageChangeListener = listener; 

<a target="_blank" href="http://vaero.blog.51cto.com/4350852/872763"> <b>Android</b><b>自定义组件(二)</b></a>

         附件工程,见(二)。

     本文转自winorlose2000 51CTO博客,原文链接:http://blog.51cto.com/vaero/872734,如需转载请自行联系原作者

继续阅读