自定义View

自定义控件的步骤:

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 地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值