自定义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 地址
- 自定义view
- 自定义View
- 自定义view
- 自定义View
- 自定义View
- 自定义view
- 自定义View
- 自定义view
- 自定义view
- 自定义View
- 自定义View
- 自定义view
- 自定义view
- 自定义view
- 自定义view
- 自定义view
- 自定义View
- 自定义View
- js中this
- 【t003】string
- form表单验证validform
- 基于DragonBoard 410c的环保机器人系统一之人机界面(上)
- 问题:Cannot find /usr/local/tomcat7/bin/setclasspath.sh
- 自定义View
- vim ----> visual studio
- ZigBee单播通信原理、串口配置 (对于理解收发数据的过程非常重要)
- 控件的监听事件。
- NSOperation NSOperationQueue
- 线程同步与互斥:读写锁
- Java 使用 Redis
- 第一天
- x86主机搭建家庭智能路由系统 ---- Proxmox虚拟化实现一机多用