自定义View

来源:互联网 发布:flash cs5 mac中文版 编辑:程序博客网 时间:2024/06/06 09:00

自定义控件的步骤:

1:自定义属性的声明和获取

2:onMeasure 测量

3:onLayout布局(在自定义ViewGroup中使用)

4:onDraw 绘制

5:onTouchEvent

6:onInterceptTouchEvent(ViewGroup中想去拦截子View的事件)

自定义属性的声明和获取

1、分析需要的自定义属性

2、在res/values/attra.xml定义声明

3、在layout xml文件中使用

4、在View的构造方法中获取属性

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="SlantedTextView">        <attr name="slantedTextSize" format="dimension"/>        <attr name="slantedBackgroundColor" format="color"/>        <attr name="slantedText" format="string"/>        <attr name="slantedTextColor" format="color"/>        <attr name="slantedLength" format="dimension"/>        <attr name="slantedMode" format="enum">            <enum name="left" value="0"></enum>            <enum name="right" value="1"></enum>            <enum name="left_bottom" value="2"></enum>            <enum name="right_bottom" value="3"></enum>            <enum name="left_triangle" value="4"></enum>            <enum name="right_triangle" value="5"></enum>            <enum name="left_bottom_triangle" value="6"></enum>            <enum name="right_bottom_triangle" value="7"></enum>        </attr>    </declare-styleable></resources>
 <com.test.SlantedTextView        android:id="@+id/slv_right_bt"        android:layout_width="@dimen/text_height"        android:layout_height="@dimen/text_height"        app:slantedBackgroundColor="@color/slanted_fb"        app:slantedMode="right_bottom"        app:slantedLength="@dimen/text_slanted"        app:slantedTextSize="10sp"        app:slantedText="JS"        android:layout_alignBottom="@+id/img_right_bt"        android:layout_alignParentEnd="true"/>
public void init(AttributeSet attrs) {        TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.SlantedTextView);        mTextSize = array.getDimension(R.styleable.SlantedTextView_slantedTextSize, mTextSize);        mTextColor = array.getColor(R.styleable.SlantedTextView_slantedTextColor, mTextColor);        mSlantedLength = array.getDimension(R.styleable.SlantedTextView_slantedLength, mSlantedLength);        mSlantedBackgroundColor = array.getColor(R.styleable.SlantedTextView_slantedBackgroundColor, mSlantedBackgroundColor);        if (array.hasValue(R.styleable.SlantedTextView_slantedText)) {            mSlantedText = array.getString(R.styleable.SlantedTextView_slantedText);        }        if (array.hasValue(R.styleable.SlantedTextView_slantedMode)) {            mMode = array.getInt(R.styleable.SlantedTextView_slantedMode, 0);        }        array.recycle();}

onMeasure 测量

MeasureSpec

MeasureSpec代表一个32位的int值,高2位是SpecMode,测量模式,低30位是SpecSize,指在某种测量模式下的规格大小。

测量的模式:EXACTLY(明确设置的一个值,比如100dp),AT_MOST(最多不超过,wrap_content时使用),UNSPECIFIED(没有限制,ListView和ScrollView中使用)

使用requestLayout()方法当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view重新调用他的onMeasure onLayout来对重新设置自己位置。特别的当view的layoutparameter发生改变,并且它的值还没能应用到view上,这时候适合调用这个方法。

setMeasuredDimension()会设置View的宽和高,我们只需要看getDefaultSize()方法,返回的一般就是specSize。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {                                    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
public static int getDefaultSize(int size, int measureSpec) {        int result = size;        int specMode = MeasureSpec.getMode(measureSpec);        int specSize = MeasureSpec.getSize(measureSpec);        switch (specMode) {        case MeasureSpec.UNSPECIFIED:            result = size;            break;        case MeasureSpec.AT_MOST:        case MeasureSpec.EXACTLY:            result = specSize;            break;        }        return result;    }
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {        boolean optical = isLayoutModeOptical(this);        if (optical != isLayoutModeOptical(mParent)) {            Insets insets = getOpticalInsets();            int opticalWidth  = insets.left + insets.right;            int opticalHeight = insets.top  + insets.bottom;            measuredWidth  += optical ? opticalWidth  : -opticalWidth;            measuredHeight += optical ? opticalHeight : -opticalHeight;        }        setMeasuredDimensionRaw(measuredWidth, measuredHeight);    }
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {        mMeasuredWidth = measuredWidth;        mMeasuredHeight = measuredHeight;        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;    }

onLayout布局

1、父控件决定子View的位置

2、尽可能将onMeasure中的一些操作移动到此方法中

public void layout(int l, int t, int r, int b) {        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;        }        int oldL = mLeft;        int oldT = mTop;        int oldB = mBottom;        int oldR = mRight;        boolean changed = isLayoutModeOptical(mParent) ?                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {            onLayout(changed, l, t, r, b);            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnLayoutChangeListeners != null) {                ArrayList<OnLayoutChangeListener> listenersCopy =                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();                int numListeners = listenersCopy.size();                for (int i = 0; i < numListeners; ++i) {                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);                }            }        }        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;    }

通过setFrame方法设定View的四个顶点位置,View在父容器中的位置也就确定了。接着会调用onLayout方法,用来确定父容器中子元素的位置。

onDraw 绘制

1、专门绘制内容区域

2、Canvas.drawXXX 绘制过程中主要是使用Canvas的相关API,要熟悉这些

3、invalidate(),postInvalidate() 重新绘制

onTouchEvent

1、主要判断MotionEvent的ACTION_DOWN、ACTION_MOVE、ACTION_UP操作,并做相对应的响应

2、如果是多点触控,就要使用ACTION_POINTER_DWON和ACTION_POINTER_UP,并设置一个ActivePointer(多点触控时实际上起作用的)

onInterceptTouchEvent

一般在ViewGroup中决定是否要拦截该手势,返回true表示拦截该手势,比如在ScrollView中是否拦截ACTION_MOVE,是否该滑动

    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        switch (action & MotionEvent.ACTION_MASK) {            case MotionEvent.ACTION_MOVE: {                final int activePointerId = mActivePointerId;                if (activePointerId == INVALID_POINTER) {                    break;                }                final int pointerIndex = ev.findPointerIndex(activePointerId);                if (pointerIndex == -1) {                    break;                }                final int y = (int) ev.getY(pointerIndex);                final int yDiff = Math.abs(y - mLastMotionY);                if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {                    mIsBeingDragged = true;  //返回true,拦截事件                    mLastMotionY = y;                    initVelocityTrackerIfNotExists();                    mVelocityTracker.addMovement(ev);                    mNestedYOffset = 0;                    if (mScrollStrictSpan == null) {                        mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");                    }                    final ViewParent parent = getParent();                    if (parent != null) {                        parent.requestDisallowInterceptTouchEvent(true);                    }                }                break;            }            case MotionEvent.ACTION_DOWN: {                break;            }            case MotionEvent.ACTION_CANCEL:            case MotionEvent.ACTION_UP:                break;            case MotionEvent.ACTION_POINTER_UP:                break;        }        return mIsBeingDragged;    }

自定义ProgressBar Demo 地址

1 0
原创粉丝点击