博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 自定义View实现画背景和前景(ViewGroup篇)
阅读量:6253 次
发布时间:2019-06-22

本文共 8684 字,大约阅读时间需要 28 分钟。

hot3.png

        在定义ListView的Selector时候,有个drawSelectorOnTop的属性,如果drawSelectorOnTop为true的话,Selector的效果是画在List Item的上面(Selector是盖住了ListView的文字或者图片),即Foreground前景。如果drawSelectorOnTop为false的话,Selector的效果是画在List Item的下面,即Background背景。由于项目中恰好需要自定义View,需要实现此效果。

       本文借ListView的代码来剖析一下,

       ListView完成此部分功能在frameworks\base\core\java\android\widget\AbsListView.java文件中。

用mSelector即ListView要画的Selector(资源文件),而mSelectorRect则是想要画的区域。

 /**     * Indicates whether the list selector should be drawn on top of the children or behind     */    boolean mDrawSelectorOnTop = false; 决定画前景还是背景    /**     * The drawable used to draw the selector     */    Drawable mSelector; ListView用中来显示Selector的Drawable,即ListSelector对应的XML文件    /**     * The current position of the selector in the list.     */    int mSelectorPosition = INVALID_POSITION;    /**     * Defines the selector's location and dimension at drawing time     */    Rect mSelectorRect = new Rect(); 用来画Selector的区域,即Selector画的位置

AbsListView中构造方法中有获取selector

Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector);        if (d != null) {            setSelector(d);        }        //默认为false,画的是背景        mDrawSelectorOnTop = a.getBoolean(                com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false);
下面看一下setSelector是如何实现的
/**     * Controls whether the selection highlight drawable should be drawn on top of the item or     * behind it.     *     * @param onTop If true, the selector will be drawn on the item it is highlighting. The default     *        is false.     *     * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop     */    public void setDrawSelectorOnTop(boolean onTop) { //提供是否画前景或者背景的接口        mDrawSelectorOnTop = onTop;    }    /**     * Set a Drawable that should be used to highlight the currently selected item.     *     * @param resID A Drawable resource to use as the selection highlight.     *     * @attr ref android.R.styleable#AbsListView_listSelector     */    public void setSelector(int resID) {        setSelector(getResources().getDrawable(resID)); 设置listSelector的XML文件    }    public void setSelector(Drawable sel) {        if (mSelector != null) {            mSelector.setCallback(null);            unscheduleDrawable(mSelector);        }        mSelector = sel;        Rect padding = new Rect();        sel.getPadding(padding);        mSelectionLeftPadding = padding.left;        mSelectionTopPadding = padding.top;        mSelectionRightPadding = padding.right;        mSelectionBottomPadding = padding.bottom;        sel.setCallback(this); //需要给Selector设置Callback        updateSelectorState();     }    /**     * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the     * selection in the list.     *     * @return the drawable used to display the selector     */    public Drawable getSelector() {        return mSelector;    }    void updateSelectorState() {        if (mSelector != null) {            if (shouldShowSelector()) {                mSelector.setState(getDrawableState());//更新Selector的状态            } else {                mSelector.setState(StateSet.NOTHING);            }        }    }

这样就将Selector设置给ListView了,并且更新了drawable的状态。

接下来我们再看一下Android是如何将drawable画到ListView的Item上的。

在AbsListView中有个onTouchEvent的方法用来处理Touch事件,其中有一段代码就是确定Selector要画的区域。

if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {                        final Handler handler = getHandler();                        if (handler != null) {                            handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?                                    mPendingCheckForTap : mPendingCheckForLongPress);                        }                        mLayoutMode = LAYOUT_NORMAL;                        if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {                            mTouchMode = TOUCH_MODE_TAP;                            setSelectedPositionInt(mMotionPosition);                            layoutChildren();                            child.setPressed(true);//设置List Item状态为 pressed                            positionSelector(mMotionPosition, child);//确定画Selector的区域                            setPressed(true); //设置ListView 的状态为pressed                            if (mSelector != null) {                                Drawable d = mSelector.getCurrent();                                if (d != null && d instanceof TransitionDrawable) {                                    ((TransitionDrawable) d).resetTransition();                                }                            }                            if (mTouchModeReset != null) {                                removeCallbacks(mTouchModeReset);                            }                            mTouchModeReset = new Runnable() {                                @Override                                public void run() {                                    mTouchMode = TOUCH_MODE_REST;                                    child.setPressed(false);                                    setPressed(false);                                    if (!mDataChanged) {                                        performClick.run();                                    }                                }                            };                            postDelayed(mTouchModeReset,                                    ViewConfiguration.getPressedStateDuration());                        } else {                            mTouchMode = TOUCH_MODE_REST;                            updateSelectorState();                        }                        return true;                    } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {                        performClick.run();                    }                }

接下来看看positionSelector的实现,

void positionSelector(int position, View sel) {        if (position != INVALID_POSITION) {            mSelectorPosition = position;        }        //设置Selector的区域为List Item View的边界        final Rect selectorRect = mSelectorRect;   selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());        if (sel instanceof SelectionBoundsAdjuster) {            ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);        }        positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,                selectorRect.bottom);        final boolean isChildViewEnabled = mIsChildViewEnabled;        if (sel.isEnabled() != isChildViewEnabled) {            mIsChildViewEnabled = !isChildViewEnabled;            if (getSelectedItemPosition() != INVALID_POSITION) {                refreshDrawableState();//根据View状态更新drawable的状态            }        }    }    private void positionSelector(int l, int t, int r, int b) {        mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r                + mSelectionRightPadding, b + mSelectionBottomPadding);    }

好了现在已经决定了将selector画在哪里,Selector的状态也已经更新OK。

还差一步没有做,那就是到底是将其怎么画上面的呢?

答案就在AbsListView.java里的dispatchDraw方法里面。

@Override    protected void dispatchDraw(Canvas canvas) {        int saveCount = 0;        final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;        if (clipToPadding) {            saveCount = canvas.save();            final int scrollX = mScrollX;            final int scrollY = mScrollY;            canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,                    scrollX + mRight - mLeft - mPaddingRight,                    scrollY + mBottom - mTop - mPaddingBottom);            mGroupFlags &= ~CLIP_TO_PADDING_MASK;        }        final boolean drawSelectorOnTop = mDrawSelectorOnTop;        if (!drawSelectorOnTop) { //将Selector画为背景            drawSelector(canvas);        }        super.dispatchDraw(canvas);// 用Canvas画ListView        if (drawSelectorOnTop) { //将Selector画为前景            drawSelector(canvas);        }        if (clipToPadding) {            canvas.restoreToCount(saveCount);            mGroupFlags |= CLIP_TO_PADDING_MASK;        }    }    private void drawSelector(Canvas canvas) {        if (!mSelectorRect.isEmpty()) {            final Drawable selector = mSelector;            selector.setBounds(mSelectorRect);//设置drawable画的区域            selector.draw(canvas); //使用canvas将drawable画上去        }    }

看到这里,想必大家都已经明白如何画前景和背景了吧。在dispatchDraw之前调用就是画前景,在dispatchDraw之后调用就是画背景。

另外补充一下,本文并没有介绍动画部分,有兴趣的可以自己研究下。

总结一下,实现这个功能需要有三个步骤:

1.设置Selector,并更新状态(初始化时候)

2.确定Selector画的区域,设置View的状态,根据View状态,更新Selector的状态(一般是对Event的处理方法中)

3.使用Canvas在dispatchDraw中,将Selector画上去,画Drawable的时候需要先设置区域,再调用drawable的draw方法。

后面我再将View如何画背景和前景补上,今天就先到这里吧。

      

转载于:https://my.oschina.net/shaorongjie/blog/202291

你可能感兴趣的文章
MpVue开发之框架的搭建
查看>>
js之放大镜效果
查看>>
Cocos2d之Node类详解之节点树(一)
查看>>
023-请你说一说你知道的自动化测试框架
查看>>
response (响应对象)
查看>>
java.lang.StringBuilder源码分析
查看>>
php中的单引号与双引号详解
查看>>
java代码继承super
查看>>
Eclipse远程调试应用程序
查看>>
openj9
查看>>
继承现有的控件
查看>>
装逼语录:
查看>>
PHP函数
查看>>
[Leetcode]414. Third Maximum Number
查看>>
UTC引发时区配置和Linux系统时间和bios时间问题
查看>>
C语言32个关键字
查看>>
图像处理之canny---求梯度
查看>>
OpenGL编程轻松入门之一个简单的例子
查看>>
MVC控制器返回重定向操作
查看>>
LINUX总结
查看>>