GVKun编程网logo

[android] 切换按钮-自定义控件-拖动效果(安卓控件拖动)

15

对于想了解[android]切换按钮-自定义控件-拖动效果的读者,本文将提供新的信息,我们将详细介绍安卓控件拖动,并且为您提供关于Android滑动效果高级篇(八)——自定义控件、Android自定义

对于想了解[android] 切换按钮-自定义控件-拖动效果的读者,本文将提供新的信息,我们将详细介绍安卓控件拖动,并且为您提供关于Android 滑动效果高级篇(八)—— 自定义控件、Android 自定义控件、android 自定义控件 自定义属性详细介绍、Android 自定义控件之自定义属性的有价值信息。

本文目录一览:

[android] 切换按钮-自定义控件-拖动效果(安卓控件拖动)

[android] 切换按钮-自定义控件-拖动效果(安卓控件拖动)

 

重写ViewonTouchEvent()方法,传递进来MotionEvent对象

调用MotionEvent对象的getAction()方法,获取当前动作

switch判断一下当前动作

事件为MotionEvent.ACTION_DOWN是手指第一次触摸屏幕

事件为MotionEvent.ACTION_MOVE是手指在屏幕上移动

事件为MotionEvent.ACTION_UP是手指离开屏幕

 

当手指触摸到屏幕

定义手指最后的坐标lastX

调用MotionEvent对象的getX() 方法,得到lastX的值

 

当手指在屏幕上移动

定义手指横向移动的距离dis

调用getX()-lastX就是移动的距离

定义滑动按钮的左边就是这个移动的距离

 

判断slideBtnLeft位于合理的位置,0到背景图的宽度-滑动按钮的宽度

调用invalidate()方法,刷新视图

 

onClick事件和onTouchEvent是有冲突

定义一个标志isDrag变量,如果有拖动发生,就把这个变量赋值true

onCllick()方法里面对这个变量进行判断

 

当手指抬起的时候

判断当前slideBtnLeft来确定当前按钮是开还是关的状态

slideBtnLeft比较 maxLeft的一半就能判断当前状态

 

package com.tsh.myswitchbtn;

import android.content.Context;
 android.graphics.Bitmap;
 android.graphics.BitmapFactory;
 android.graphics.Canvas;
 android.graphics.Paint;
 android.util.AttributeSet;
 android.view.MotionEvent;
 android.view.View;
 android.view.View.OnClickListener;

public class MyToggleBtn extends View implements OnClickListener {
    //背景图片
    private Bitmap bitmapBackground;
    按钮图片
     Bitmap bitmapBtn;
     Paint paint;
    /**
     * 布局文件中使用
     * @param context
     *  attrs
     */
    public MyToggleBtn(Context context,AttributeSet attrs) {
        super(context,attrs);
        initView();
    }
    
     * 初始化view
     private void initView() {
        bitmapBackground=BitmapFactory.decodeResource(getResources(),R.drawable.switch_background);
        bitmapBtn=new Paint();
        paint.setAntiAlias(true);
        点击事件
        setonClickListener(this);
    }
    
     * 计算大小
     */
    @Override
    protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
        setMeasuredDimension(bitmapBackground.getWidth(),bitmapBackground.getHeight());
    }
    当前状态
    boolean currentState=false;
    滑动按钮的当前left
    float slideBtnLeft=0
     * 绘制view
      onDraw(Canvas canvas) {
        绘制背景
        canvas.drawBitmap(bitmapBackground,0,paint);
        绘制滑动按钮
        canvas.drawBitmap(bitmapBtn,slideBtnLeft,paint);
    }
    boolean isDrag=
     * 点击事件
      onClick(View v) {
        解决与移动事件的冲突
        if(!isDrag){
            currentState = !currentState;
            flushState();
        }

    }

     lastX;
    
     * 触摸事件
      firstX;
    @Override
    boolean onTouchEvent(MotionEvent event) {
        .onTouchEvent(event);
        switch(event.getAction()){
            手指按下
            case MotionEvent.ACTION_DOWN:
                firstX=lastX=() event.getX();
                isDrag=;
            break手指移动
             MotionEvent.ACTION_MOVE:
                解决与点击事件冲突
                if(Math.abs(event.getX()-firstX)>5){
                    isDrag=;
                }
                
                int dis=(int) event.getX()-lastX;
                slideBtnLeft=slideBtnLeft+dis;
                lastX=() event.getX();
            手指抬起
             MotionEvent.ACTION_UP:
                if(isDrag){
                    int maxLeft = bitmapBackground.getWidth()
                            - bitmapBtn.getWidth();
                    if (slideBtnLeft >= maxLeft / 2) {
                        currentState = ;
                    } else {
                        currentState = ;
                    }
                    flushState();
                }
            ;
        }
        flushView();
        
        return ;
    }
    
     * 刷新状态
      flushState() {
        if (currentState == ) {
            slideBtnLeft = bitmapBackground.getWidth()
                    - bitmapBtn.getWidth();
        }  {
            slideBtnLeft = 0;
        }
        invalidate();
    }
    
     * 刷新视图
      flushView() {
        int maxLeft=bitmapBackground.getWidth()-bitmapBtn.getWidth();
        slideBtnLeft=(slideBtnLeft>0) ? slideBtnLeft : 0;
        slideBtnLeft=(slideBtnLeft<maxLeft) ? slideBtnLeft:maxLeft;
        invalidate();
    }
}

 

Android 滑动效果高级篇(八)—— 自定义控件

Android 滑动效果高级篇(八)—— 自定义控件

自定义控件,较常用 View、ViewGroup、Scroller 三个类,其继承关系如下:


本示例自定义控件,实现一个 Gallery 效果,并添加了一个显示 View 个数和位置的 bar 条,效果图:



自定义控件,包含通过继承实现的自定义控件和自定义控件属性两部分,即控件和属性

1、自定义属性

自定义属性,分为定义属性、解析属性、设置属性三部分,具体步骤:

首先,在 res/valus/attrs.xml 属性资源文件中,定义控件属性

源码打印?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <declare-styleable name="com.myapps.widget.Pager">  
  4.         <attr name="pageWidth" format="dimension" />  
  5.     </declare-styleable>  
  6.       
  7.     <declare-styleable name="com.myapps.widget.PagerBar">  
  8.         <attr name="barColor" format="color" />  
  9.         <attr name="highlightColor" format="color" />  
  10.         <attr name="fadeDelay" format="integer" />  
  11.         <attr name="fadeDuration" format="integer" />  
  12.         <attr name="roundRectRadius" format="dimension" />  
  13.     </declare-styleable>  
  14. </resources>  
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="com.myapps.widget.Pager">
        <attr name="pageWidth" format="dimension" />
    </declare-styleable>
    
    <declare-styleable name="com.myapps.widget.PagerBar">
        <attr name="barColor" format="color" />
        <attr name="highlightColor" format="color" />
        <attr name="fadeDelay" format="integer" />
        <attr name="fadeDuration" format="integer" />
        <attr name="roundRectRadius" format="dimension" />
    </declare-styleable>
</resources>

然后,在自定义控件的代码中,解析自定义的属性,如在 PagerBar.java:

源码打印?
  1. // 自定义属性  
  2.         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_PagerBar);  
  3.         int barBackColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_barColor, DEFAULT_BAR_BACKCOLOR);              // bar 背景色  
  4.         int barForeColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_highlightColor, DEFAULT_BAR_FORECOLOR);    // bar 前景色  
  5.         fadeDelay = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDelay, DEFAULT_FADE_DELAY);             // bar 消失延迟时间  
  6.         fadeDuration = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDuration, DEFAULT_FADE_DURATION);    // bar 消失动画时间  
  7.         ovalRadius = a.getDimension(R.styleable.com_myapps_widget_PagerBar_roundRectRadius, 2f);  
  8.         a.recycle();  
// 自定义属性
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_PagerBar);
		int barBackColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_barColor, DEFAULT_BAR_BACKCOLOR);				// bar背景色
		int barForeColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_highlightColor, DEFAULT_BAR_FORECOLOR);	// bar前景色
		fadeDelay = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDelay, DEFAULT_FADE_DELAY);				// bar消失延迟时间
		fadeDuration = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDuration, DEFAULT_FADE_DURATION);	// bar消失动画时间
		ovalRadius = a.getDimension(R.styleable.com_myapps_widget_PagerBar_roundRectRadius, 2f);
		a.recycle();

最后,在布局文件中设置属性,如在 main.xml

源码打印?
  1. <com.homer.mycontrol.PagerBar  
  2.     android:id="@+id/control"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="4dip"  
  5.     android:layout_margin="8dip"  
  6.     myapps:roundRectRadius="2dip" />  <!-- 自定义圆角 -->  
    <com.homer.mycontrol.PagerBar
        android:id="@+id/control"
        android:layout_width="fill_parent"
        android:layout_height="4dip"
        android:layout_margin="8dip"
        myapps:roundRectRadius="2dip" />	 <!-- 自定义圆角 -->

其中,在布局中间 main.xml 中,需要注意:

xmlns:myapps="http://schemas.android.com/apk/res/com.homer.mycontrol"

定义属性时,在 declare-styleable 的 name 中,需要包含 com.myapps.widget.PagerBar,表示自定义的控件 PageBar 是 widget 子类,myapps 是 xmlns 解析标记

解析属性时,在 TypedArray 中,需要包含 R.styleable.com_myapps_widget_PagerBar,横线替换了圆点.

定义属性时,在 com.homer.mycontrol.PagerBar 中,需要包含 myapps:roundRectRadius="2dip",加上 myapps 解析标记


2、自定义控件 PagerBar

自定义 PagerBar,在图片下方用来显示图片滑到了第几页,即上面效果图(图 2、图 3)中的下部银白色细条,具体实现:

源码打印?
  1. public PagerBar(Context context, AttributeSet attrs, int defStyle) {  
  2.     super(context, attrs, defStyle);  
  3.   
  4.     // 自定义属性  
  5.     TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_PagerBar);  
  6.     int barBackColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_barColor, DEFAULT_BAR_BACKCOLOR);              // bar 背景色  
  7.     int barForeColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_highlightColor, DEFAULT_BAR_FORECOLOR);    // bar 前景色  
  8.     fadeDelay = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDelay, DEFAULT_FADE_DELAY);             // bar 消失延迟时间  
  9.     fadeDuration = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDuration, DEFAULT_FADE_DURATION);    // bar 消失动画时间  
  10.     ovalRadius = a.getDimension(R.styleable.com_myapps_widget_PagerBar_roundRectRadius, 2f);  
  11.     a.recycle();  
  12.   
  13.     barBackPaint = new Paint();  
  14.     barBackPaint.setColor(barBackColor);  
  15.   
  16.     barForePaint = new Paint();  
  17.     barForePaint.setColor(barForeColor);  
  18.   
  19.     fadeOutAnimation = new AlphaAnimation(1f, 0f);  
  20.     fadeOutAnimation.setDuration(fadeDuration);  
  21.     fadeOutAnimation.setRepeatCount(0);  
  22.     fadeOutAnimation.setInterpolator(new LinearInterpolator());  
  23.     fadeOutAnimation.setFillEnabled(true);  
  24.     fadeOutAnimation.setFillAfter(true);  
  25. }  
  26.   
  27. public int getNumPages() {  
  28.     return numPages;  
  29. }  
  30.   
  31. public void setNumPages(int numPages) {  
  32.     if (numPages <= 0) {  
  33.         throw new IllegalArgumentException("numPages must be positive");  
  34.     }  
  35.     this.numPages = numPages;  
  36.     invalidate();       // 重绘 View  
  37.     fadeOut();          // 设置 bar 消失效果  
  38. }  
  39.   
  40. /** bar 消失动画 */  
  41. private void fadeOut() {  
  42.     if (fadeDuration > 0) {  
  43.         clearAnimation();  
  44.         fadeOutAnimation.setStartTime(AnimationUtils.currentAnimationTimeMillis() + fadeDelay); // 延迟 fadeDelay 后动画开始  
  45.         setAnimation(fadeOutAnimation);  
  46.     }  
  47. }  
  48.   
  49. /**  @return  0 to numPages-1 */  
  50. public int getCurrentPage() {  
  51.     return currentPage;  
  52. }  
  53.   
  54. /** @param currentPage  0 to numPages-1  */  
  55. public void setCurrentPage(int currentPage) {  
  56.     if (currentPage < 0 || currentPage >= numPages) {  
  57.         throw new IllegalArgumentException("currentPage parameter out of bounds");  
  58.     }  
  59.     if (this.currentPage != currentPage) {  
  60.         this.currentPage = currentPage;  
  61.         this.position = currentPage * getPageWidth();   // bar 前景色滑动条的起始位置(像素值)  
  62.         invalidate();  
  63.         fadeOut();  
  64.     }  
  65. }  
  66.   
  67. /** 获取 View 的宽度,即 bar 的宽度 */  
  68. public int getPageWidth() {  
  69.     return getWidth() / numPages;   // getWidth () 是 PagerBar 的宽度(减去了 margin 左右距离后)  
  70. }  
  71.   
  72. /**  @param position     can be -pageWidth to pageWidth*(numPages+1)  */  
  73. public void setPosition(int position) {  
  74.     if (this.position != position) {  
  75.         this.position = position;  
  76.         invalidate();  
  77.         fadeOut();  
  78.     }  
  79. }  
  80.   
  81. @Override  
  82. protected void onDraw(Canvas canvas) {  
  83.     canvas.drawRoundRect(new RectF(00, getWidth(), getHeight()), ovalRadius, ovalRadius, barBackPaint);   // 绘制 bar 背景  
  84.     canvas.drawRoundRect(new RectF(position, 0, position + (getWidth() / numPages), getHeight()), ovalRadius, ovalRadius, barForePaint);    // 绘制 bar 前景  
  85. }  
	public PagerBar(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		// 自定义属性
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_PagerBar);
		int barBackColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_barColor, DEFAULT_BAR_BACKCOLOR);				// bar背景色
		int barForeColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_highlightColor, DEFAULT_BAR_FORECOLOR);	// bar前景色
		fadeDelay = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDelay, DEFAULT_FADE_DELAY);				// bar消失延迟时间
		fadeDuration = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDuration, DEFAULT_FADE_DURATION);	// bar消失动画时间
		ovalRadius = a.getDimension(R.styleable.com_myapps_widget_PagerBar_roundRectRadius, 2f);
		a.recycle();

		barBackPaint = new Paint();
		barBackPaint.setColor(barBackColor);

		barForePaint = new Paint();
		barForePaint.setColor(barForeColor);

		fadeOutAnimation = new AlphaAnimation(1f, 0f);
		fadeOutAnimation.setDuration(fadeDuration);
		fadeOutAnimation.setRepeatCount(0);
		fadeOutAnimation.setInterpolator(new LinearInterpolator());
		fadeOutAnimation.setFillEnabled(true);
		fadeOutAnimation.setFillAfter(true);
	}

	public int getNumPages() {
		return numPages;
	}

	public void setNumPages(int numPages) {
		if (numPages <= 0) {
			throw new IllegalArgumentException("numPages must be positive");
		}
		this.numPages = numPages;
		invalidate();		// 重绘View
		fadeOut();			// 设置bar消失效果
	}

	/** bar消失动画 */
	private void fadeOut() {
		if (fadeDuration > 0) {
			clearAnimation();
			fadeOutAnimation.setStartTime(AnimationUtils.currentAnimationTimeMillis() + fadeDelay);	//延迟fadeDelay后动画开始
			setAnimation(fadeOutAnimation);
		}
	}

	/**  @return  0 to numPages-1 */
	public int getCurrentPage() {
		return currentPage;
	}

	/** @param currentPage  0 to numPages-1  */
	public void setCurrentPage(int currentPage) {
		if (currentPage < 0 || currentPage >= numPages) {
			throw new IllegalArgumentException("currentPage parameter out of bounds");
		}
		if (this.currentPage != currentPage) {
			this.currentPage = currentPage;
			this.position = currentPage * getPageWidth();	// bar前景色滑动条的起始位置(像素值)
			invalidate();
			fadeOut();
		}
	}

	/** 获取View的宽度,即bar的宽度 */
	public int getPageWidth() {
		return getWidth() / numPages;	// getWidth()是PagerBar的宽度(减去了margin左右距离后)
	}

	/**  @param position     can be -pageWidth to pageWidth*(numPages+1)  */
	public void setPosition(int position) {
		if (this.position != position) {
			this.position = position;
			invalidate();
			fadeOut();
		}
	}

	@Override
	protected void onDraw(Canvas canvas) {
		canvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()), ovalRadius, ovalRadius, barBackPaint);	// 绘制bar背景
		canvas.drawRoundRect(new RectF(position, 0, position + (getWidth() / numPages), getHeight()), ovalRadius, ovalRadius, barForePaint);	// 绘制bar前景
	}
}


3、自定义控件 Pager

自定义控件 Pager,继承自 ViewGroup,用来显示图片的,类似于 Gallery,实现主要部分包含:

A、自定义属性解析

B、Pager 容器控件 Scroller 滑动页设置与控制

C、容器状态保存(onSaveInstanceState)

D、容器事件监听接口


详细实现如下:

A、自定义属性解析

源码打印?
  1. public Pager(Context context, AttributeSet attrs, int defStyle) {  
  2.     super(context, attrs, defStyle);  
  3.   
  4.     // 自定义属性  
  5.     TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_Pager);  
  6.     pageWidthSpec = a.getDimensionPixelSize(R.styleable.com_myapps_widget_Pager_pageWidth, SPEC_UNDEFINED);  
  7.     a.recycle();  
  8. }  
	public Pager(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		// 自定义属性
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_Pager);
		pageWidthSpec = a.getDimensionPixelSize(R.styleable.com_myapps_widget_Pager_pageWidth, SPEC_UNDEFINED);
		a.recycle();
	}


B、Pager 容器控件 Scroller 滑动页设置与控制

源码打印?
  1. public void setCurrentPage(int currentPage) {  
  2.     mCurrentPage = Math.max(0, Math.min(currentPage, getChildCount()));     // 非常好  
  3.     scrollTo(getScrollXForPage(mCurrentPage), 0);  
  4.     invalidate();   // 重绘 View  
  5. }  
  6.   
  7. int getCurrentPage() {  
  8.     return mCurrentPage;  
  9. }  
  10.   
  11. public void setPageWidth(int pageWidth) {  
  12.     this.pageWidthSpec = pageWidth;  
  13. }  
  14.   
  15. public int getPageWidth() {  
  16.     return pageWidth;  
  17. }  
  18.   
  19. /** 获取 whichPage 的 Pager 起始 x 位置,whichPage 从 0 开始计 */  
  20. private int getScrollXForPage(int whichPage) {  
  21.     return (whichPage * pageWidth) - pageWidthPadding();  
  22. }  
  23.   
  24. /** 返回 View 的 paddingwidth 一半 (1/2)*/  
  25. int pageWidthPadding() {  
  26.     return ((getMeasuredWidth() - pageWidth) / 2);  
  27. }  
  28.   
  29. @Override  
  30. public void computeScroll() {       // update  mScrollX and mScrollY  of View  
  31.     if (mScroller.computeScrollOffset()) {  
  32.         scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
  33.         postInvalidate();           // invalidate the View from a non-UI thread  
  34.     } else if (mNextPage != INVALID_SCREEN) {  
  35.         mCurrentPage = mNextPage;  
  36.         mNextPage = INVALID_SCREEN;  
  37.         clearChildrenCache();  
  38.     }  
  39. }  
  40.   
  41. @Override  
  42. protected void dispatchDraw(Canvas canvas) {    // draw the child views  
  43.       
  44.     final long drawingTime = getDrawingTime();  // 绘制 childView  
  45.     final int count = getChildCount();  
  46.     for (int i = 0; i < count; i++) {  
  47.         drawChild(canvas, getChildAt(i), drawingTime);  
  48.     }  
  49.   
  50.     for (OnScrollListener mListener : mListeners) { // 自定义接口  
  51.         int adjustedScrollX = getScrollX() + pageWidthPadding();  
  52.         mListener.onScroll(adjustedScrollX);  
  53.         if (adjustedScrollX % pageWidth == 0) { // scroll finished  
  54.             mListener.onViewScrollFinished(adjustedScrollX / pageWidth);  
  55.         }  
  56.     }  
  57. }  
  58.   
  59. @Override  
  60. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  61.     super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  62.   
  63.     pageWidth = (pageWidthSpec == SPEC_UNDEFINED) ? getMeasuredWidth() : pageWidthSpec;  
  64.     pageWidth = Math.min(pageWidth, getMeasuredWidth());  
  65.   
  66.     final int count = getChildCount();  
  67.     for (int i = 0; i < count; i++) {  
  68.         widthMeasureSpec = MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY);  
  69.         getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);  
  70.     }  
  71.   
  72.     if (mFirstLayout) { // 第一次显示 Pager 时,page 的位置  
  73.         scrollTo(getScrollXForPage(mCurrentPage), mScroller.getCurrY());  
  74.         mFirstLayout = false;  
  75.     }  
  76. }  
  77.   
  78. @Override  
  79. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  
  80.     int childLeft = 0;  
  81.   
  82.     final int count = getChildCount();  // 绘制 childView  
  83.     for (int i = 0; i < count; i++) {  
  84.         final View child = getChildAt(i);  
  85.         if (child.getVisibility() != View.GONE) {  
  86.             final int childWidth = child.getMeasuredWidth();  
  87.             child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());  
  88.             childLeft += childWidth;  
  89.         }  
  90.     }  
  91. }  
	public void setCurrentPage(int currentPage) {
		mCurrentPage = Math.max(0, Math.min(currentPage, getChildCount()));		// 非常好
		scrollTo(getScrollXForPage(mCurrentPage), 0);
		invalidate();	// 重绘View
	}

	int getCurrentPage() {
		return mCurrentPage;
	}

	public void setPageWidth(int pageWidth) {
		this.pageWidthSpec = pageWidth;
	}

	public int getPageWidth() {
		return pageWidth;
	}

	/** 获取whichPage的Pager起始x位置,whichPage从0开始计 */
	private int getScrollXForPage(int whichPage) {
		return (whichPage * pageWidth) - pageWidthPadding();
	}

	/** 返回View的 paddingwidth 一半(1/2)*/
	int pageWidthPadding() {
		return ((getMeasuredWidth() - pageWidth) / 2);
	}

	@Override
	public void computeScroll() {		// update  mScrollX and mScrollY  of View
		if (mScroller.computeScrollOffset()) {
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			postInvalidate();			// invalidate the View from a non-UI thread
		} else if (mNextPage != INVALID_SCREEN) {
			mCurrentPage = mNextPage;
			mNextPage = INVALID_SCREEN;
			clearChildrenCache();
		}
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {	// draw the child views
		
		final long drawingTime = getDrawingTime();	// 绘制childView
		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			drawChild(canvas, getChildAt(i), drawingTime);
		}

		for (OnScrollListener mListener : mListeners) {	// 自定义接口
			int adjustedScrollX = getScrollX() + pageWidthPadding();
			mListener.onScroll(adjustedScrollX);
			if (adjustedScrollX % pageWidth == 0) {	// scroll finished
				mListener.onViewScrollFinished(adjustedScrollX / pageWidth);
			}
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		pageWidth = (pageWidthSpec == SPEC_UNDEFINED) ? getMeasuredWidth() : pageWidthSpec;
		pageWidth = Math.min(pageWidth, getMeasuredWidth());

		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			widthMeasureSpec = MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY);
			getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
		}

		if (mFirstLayout) {	// 第一次显示Pager时,page的位置
			scrollTo(getScrollXForPage(mCurrentPage), mScroller.getCurrY());
			mFirstLayout = false;
		}
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
		int childLeft = 0;

		final int count = getChildCount();	// 绘制childView
		for (int i = 0; i < count; i++) {
			final View child = getChildAt(i);
			if (child.getVisibility() != View.GONE) {
				final int childWidth = child.getMeasuredWidth();
				child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
				childLeft += childWidth;
			}
		}
	}

源码打印?
  1. @Override  
  2. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  3.     final int action = ev.getAction();  
  4.     if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) { // 正在滑动中  
  5.         return true;  
  6.     }  
  7.   
  8.     final float x = ev.getX();  
  9.     final float y = ev.getY();  
  10.   
  11.     switch (action) {  
  12.     case MotionEvent.ACTION_MOVE:  
  13.         if (mTouchState == TOUCH_STATE_REST) {  
  14.             checkStartScroll(x, y);  
  15.         }  
  16.         break;  
  17.   
  18.     case MotionEvent.ACTION_DOWN:  
  19.         mLastMotionX = x;  
  20.         mLastMotionY = y;  
  21.         mAllowLongPress = true;  
  22.   
  23.         mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;    // scroll 完成后,重置状态  
  24.         break;  
  25.   
  26.     case MotionEvent.ACTION_CANCEL:  
  27.     case MotionEvent.ACTION_UP:  
  28.         clearChildrenCache();  
  29.         mTouchState = TOUCH_STATE_REST;  
  30.         break;  
  31.     }  
  32.   
  33.     return mTouchState != TOUCH_STATE_REST;  
  34. }  
  35.   
  36. @Override  
  37. public boolean onTouchEvent(MotionEvent ev) {  
  38.     if (mVelocityTracker == null) {  
  39.         mVelocityTracker = VelocityTracker.obtain();  
  40.     }  
  41.     mVelocityTracker.addMovement(ev);  
  42.   
  43.     final int action = ev.getAction();  
  44.     final float x = ev.getX();  
  45.     final float y = ev.getY();  
  46.   
  47.     switch (action) {  
  48.     case MotionEvent.ACTION_DOWN:  
  49.         if (!mScroller.isFinished()) {  
  50.             mScroller.abortAnimation();  
  51.         }  
  52.         mLastMotionX = x;  
  53.         break;  
  54.           
  55.     case MotionEvent.ACTION_MOVE:  
  56.         if (mTouchState == TOUCH_STATE_REST) {  
  57.             checkStartScroll(x, y);  
  58.         } else if (mTouchState == TOUCH_STATE_SCROLLING) {  // scrolling 状态时,重绘 view  
  59.             int deltaX = (int) (mLastMotionX - x);  
  60.             mLastMotionX = x;  
  61.   
  62.             if (getScrollX() < 0 || getScrollX() > getChildAt(getChildCount() - 1).getLeft()) {  
  63.                 deltaX /= 2;  
  64.             }  
  65.             scrollBy(deltaX, 0);  
  66.         }  
  67.         break;  
  68.           
  69.     case MotionEvent.ACTION_UP:  
  70.         if (mTouchState == TOUCH_STATE_SCROLLING) {  
  71.             final VelocityTracker velocityTracker = mVelocityTracker;  
  72.             velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);  
  73.             int velocityX = (int) velocityTracker.getXVelocity();  
  74.   
  75.             if (velocityX > SNAP_VELOCITY && mCurrentPage > 0) {  
  76.                 snapToPage(mCurrentPage - 1);  
  77.             } else if (velocityX < -SNAP_VELOCITY && mCurrentPage < getChildCount() - 1) {  
  78.                 snapToPage(mCurrentPage + 1);  
  79.             } else {  
  80.                 snapToDestination();  
  81.             }  
  82.   
  83.             if (mVelocityTracker != null) {  
  84.                 mVelocityTracker.recycle();  
  85.                 mVelocityTracker = null;  
  86.             }  
  87.         }  
  88.         mTouchState = TOUCH_STATE_REST;  
  89.         break;  
  90.     case MotionEvent.ACTION_CANCEL:  
  91.         mTouchState = TOUCH_STATE_REST;  
  92.     }  
  93.   
  94.     return true;  
  95. }  
  96.   
  97. /** 检查 scroll 状态,并设置绘制 scroll 缓存 */  
  98. private void checkStartScroll(float x, float y) {  
  99.     final int xDiff = (int) Math.abs(x - mLastMotionX);  
  100.     final int yDiff = (int) Math.abs(y - mLastMotionY);  
  101.   
  102.     boolean xMoved = xDiff > mTouchSlop;  
  103.     boolean yMoved = yDiff > mTouchSlop;  
  104.   
  105.     if (xMoved || yMoved) {  
  106.         if (xMoved) {  
  107.             mTouchState = TOUCH_STATE_SCROLLING;        // 设置为 scrolling 状态  
  108.             enableChildrenCache();  
  109.         }  
  110.         if (mAllowLongPress) {  
  111.             mAllowLongPress = false;  
  112.             final View currentScreen = getChildAt(mCurrentPage);  
  113.             currentScreen.cancelLongPress();    // Cancels a pending long press  
  114.         }  
  115.     }  
  116. }  
  117.   
  118. void enableChildrenCache() {  
  119.     setChildrenDrawingCacheEnabled(true);       // Enables or disables the drawing cache for each child of this viewGroup  
  120.     setChildrenDrawnWithCacheEnabled(true); // Tells the ViewGroup to draw its children using their drawing cache  
  121. }  
  122.   
  123. void clearChildrenCache() {  
  124.     setChildrenDrawnWithCacheEnabled(false);  
  125. }  
  126.   
  127. private void snapToDestination() {  
  128.     final int startX = getScrollXForPage(mCurrentPage);  
  129.     int whichPage = mCurrentPage;  
  130.     if (getScrollX() < startX - getWidth() / 8) {  
  131.         whichPage = Math.max(0, whichPage - 1);  
  132.     } else if (getScrollX() > startX + getWidth() / 8) {  
  133.         whichPage = Math.min(getChildCount() - 1, whichPage + 1);  
  134.     }  
  135.   
  136.     snapToPage(whichPage);  
  137. }  
  138.   
  139. void snapToPage(int whichPage) {  
  140.     enableChildrenCache();  
  141.   
  142.     boolean changingPages = whichPage != mCurrentPage;  
  143.   
  144.     mNextPage = whichPage;  
  145.   
  146.     View focusedChild = getFocusedChild();  
  147.     if (focusedChild != null && changingPages && focusedChild == getChildAt(mCurrentPage)) {  
  148.         focusedChild.clearFocus();  
  149.     }  
  150.   
  151.     final int newX = getScrollXForPage(whichPage);  
  152.     final int delta = newX - getScrollX();  
  153.     mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);  
  154.     invalidate();  
  155. }  
  156.   
  157. /** 向左滑动 */  
  158. public void scrollLeft() {  
  159.     if (mNextPage == INVALID_SCREEN && mCurrentPage > 0 && mScroller.isFinished()) {  
  160.         snapToPage(mCurrentPage - 1);  
  161.     }  
  162. }  
  163.   
  164. /** 向右滑动 */  
  165. public void scrollRight() {  
  166.     if (mNextPage == INVALID_SCREEN && mCurrentPage < getChildCount() - 1 && mScroller.isFinished()) {  
  167.         snapToPage(mCurrentPage + 1);  
  168.     }  
  169. }  
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		final int action = ev.getAction();
		if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {	// 正在滑动中
			return true;
		}

		final float x = ev.getX();
		final float y = ev.getY();

		switch (action) {
		case MotionEvent.ACTION_MOVE:
			if (mTouchState == TOUCH_STATE_REST) {
				checkStartScroll(x, y);
			}
			break;

		case MotionEvent.ACTION_DOWN:
			mLastMotionX = x;
			mLastMotionY = y;
			mAllowLongPress = true;

			mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;	// scroll 完成后,重置状态
			break;

		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			clearChildrenCache();
			mTouchState = TOUCH_STATE_REST;
			break;
		}

		return mTouchState != TOUCH_STATE_REST;
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(ev);

		final int action = ev.getAction();
		final float x = ev.getX();
		final float y = ev.getY();

		switch (action) {
		case MotionEvent.ACTION_DOWN:
			if (!mScroller.isFinished()) {
				mScroller.abortAnimation();
			}
			mLastMotionX = x;
			break;
			
		case MotionEvent.ACTION_MOVE:
			if (mTouchState == TOUCH_STATE_REST) {
				checkStartScroll(x, y);
			} else if (mTouchState == TOUCH_STATE_SCROLLING) {	// scrolling 状态时,重绘view
				int deltaX = (int) (mLastMotionX - x);
				mLastMotionX = x;

				if (getScrollX() < 0 || getScrollX() > getChildAt(getChildCount() - 1).getLeft()) {
					deltaX /= 2;
				}
				scrollBy(deltaX, 0);
			}
			break;
			
		case MotionEvent.ACTION_UP:
			if (mTouchState == TOUCH_STATE_SCROLLING) {
				final VelocityTracker velocityTracker = mVelocityTracker;
				velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
				int velocityX = (int) velocityTracker.getXVelocity();

				if (velocityX > SNAP_VELOCITY && mCurrentPage > 0) {
					snapToPage(mCurrentPage - 1);
				} else if (velocityX < -SNAP_VELOCITY && mCurrentPage < getChildCount() - 1) {
					snapToPage(mCurrentPage + 1);
				} else {
					snapToDestination();
				}

				if (mVelocityTracker != null) {
					mVelocityTracker.recycle();
					mVelocityTracker = null;
				}
			}
			mTouchState = TOUCH_STATE_REST;
			break;
		case MotionEvent.ACTION_CANCEL:
			mTouchState = TOUCH_STATE_REST;
		}

		return true;
	}

	/** 检查scroll状态,并设置绘制scroll缓存 */
	private void checkStartScroll(float x, float y) {
		final int xDiff = (int) Math.abs(x - mLastMotionX);
		final int yDiff = (int) Math.abs(y - mLastMotionY);

		boolean xMoved = xDiff > mTouchSlop;
		boolean yMoved = yDiff > mTouchSlop;

		if (xMoved || yMoved) {
			if (xMoved) {
				mTouchState = TOUCH_STATE_SCROLLING;		// 设置为scrolling 状态
				enableChildrenCache();
			}
			if (mAllowLongPress) {
				mAllowLongPress = false;
				final View currentScreen = getChildAt(mCurrentPage);
				currentScreen.cancelLongPress();	// Cancels a pending long press
			}
		}
	}

	void enableChildrenCache() {
		setChildrenDrawingCacheEnabled(true);		// Enables or disables the drawing cache for each child of this viewGroup
		setChildrenDrawnWithCacheEnabled(true);	// Tells the ViewGroup to draw its children using their drawing cache
	}

	void clearChildrenCache() {
		setChildrenDrawnWithCacheEnabled(false);
	}

	private void snapToDestination() {
		final int startX = getScrollXForPage(mCurrentPage);
		int whichPage = mCurrentPage;
		if (getScrollX() < startX - getWidth() / 8) {
			whichPage = Math.max(0, whichPage - 1);
		} else if (getScrollX() > startX + getWidth() / 8) {
			whichPage = Math.min(getChildCount() - 1, whichPage + 1);
		}

		snapToPage(whichPage);
	}

	void snapToPage(int whichPage) {
		enableChildrenCache();

		boolean changingPages = whichPage != mCurrentPage;

		mNextPage = whichPage;

		View focusedChild = getFocusedChild();
		if (focusedChild != null && changingPages && focusedChild == getChildAt(mCurrentPage)) {
			focusedChild.clearFocus();
		}

		final int newX = getScrollXForPage(whichPage);
		final int delta = newX - getScrollX();
		mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
		invalidate();
	}

	/** 向左滑动 */
	public void scrollLeft() {
		if (mNextPage == INVALID_SCREEN && mCurrentPage > 0 && mScroller.isFinished()) {
			snapToPage(mCurrentPage - 1);
		}
	}

	/** 向右滑动 */
	public void scrollRight() {
		if (mNextPage == INVALID_SCREEN && mCurrentPage < getChildCount() - 1 && mScroller.isFinished()) {
			snapToPage(mCurrentPage + 1);
		}
	}


C、容器状态保存(onSaveInstanceState)

源码打印?
  1. /** 保存状态 */  
  2. public static class SavedState extends BaseSavedState {  
  3.     int currentScreen = -1;  
  4.   
  5.     SavedState(Parcelable superState) {  
  6.         super(superState);  
  7.     }  
  8.   
  9.     private SavedState(Parcel in) {  
  10.         super(in);  
  11.         currentScreen = in.readInt();  
  12.     }  
  13.   
  14.     public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {  
  15.         public SavedState createFromParcel(Parcel in) { // get data written by Parcelable.writeToParcel()     
  16.             return new SavedState(in);  
  17.         }  
  18.   
  19.         public SavedState[] newArray(int size) {            // create  array of the Parcelable   
  20.             return new SavedState[size];  
  21.         }  
  22.     };  
  23.       
  24.     @Override  
  25.     public void writeToParcel(Parcel out, int flags) {      // set data to parcel  
  26.         super.writeToParcel(out, flags);  
  27.         out.writeInt(currentScreen);  
  28.     }  
  29.   
  30. }  
  31.   
  32. @Override  
  33. protected Parcelable onSaveInstanceState() {        // 保存状态  
  34.     final SavedState state = new SavedState(super.onSaveInstanceState());  
  35.     state.currentScreen = mCurrentPage;     // save InstanceState  
  36.     return state;  
  37. }  
  38.   
  39. @Override  
  40. protected void onRestoreInstanceState(Parcelable state) {   // 恢复状态  
  41.     SavedState savedState = (SavedState) state;  
  42.     super.onRestoreInstanceState(savedState.getSuperState());   // get InstanceState  
  43.     if (savedState.currentScreen != INVALID_SCREEN) {  
  44.         mCurrentPage = savedState.currentScreen;      
  45.     }  
  46. }  
	/** 保存状态 */
	public static class SavedState extends BaseSavedState {
		int currentScreen = -1;

		SavedState(Parcelable superState) {
			super(superState);
		}

		private SavedState(Parcel in) {
			super(in);
			currentScreen = in.readInt();
		}

		public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
			public SavedState createFromParcel(Parcel in) {	// get data written by Parcelable.writeToParcel()	
				return new SavedState(in);
			}

			public SavedState[] newArray(int size) {			// create  array of the Parcelable 
				return new SavedState[size];
			}
		};
		
		@Override
		public void writeToParcel(Parcel out, int flags) {		// set data to parcel
			super.writeToParcel(out, flags);
			out.writeInt(currentScreen);
		}

	}

	@Override
	protected Parcelable onSaveInstanceState() {		// 保存状态
		final SavedState state = new SavedState(super.onSaveInstanceState());
		state.currentScreen = mCurrentPage;	 	// save InstanceState
		return state;
	}

	@Override
	protected void onRestoreInstanceState(Parcelable state) {	// 恢复状态
		SavedState savedState = (SavedState) state;
		super.onRestoreInstanceState(savedState.getSuperState());	// get InstanceState
		if (savedState.currentScreen != INVALID_SCREEN) {
			mCurrentPage = savedState.currentScreen;	
		}
	}

D、容器事件监听接口

源码打印?
  1. public void addOnScrollListener(OnScrollListener listener) {  
  2.     mListeners.add(listener);  
  3. }  
  4.   
  5. public void removeOnScrollListener(OnScrollListener listener) {  
  6.     mListeners.remove(listener);  
  7. }  
  8.   
  9. /** 自定义接口 */  
  10. public static interface OnScrollListener {  
  11.     void onScroll(int scrollX);  
  12.     void onViewScrollFinished(int currentPage);  
  13. }  
	public void addOnScrollListener(OnScrollListener listener) {
		mListeners.add(listener);
	}

	public void removeOnScrollListener(OnScrollListener listener) {
		mListeners.remove(listener);
	}

	/** 自定义接口 */
	public static interface OnScrollListener {
		void onScroll(int scrollX);
		void onViewScrollFinished(int currentPage);
	}


代码下载



参考推荐:

Android 中自定义属性的使用

Android 中自定义属性的格式详解

Scroller(Android)  Scroller(cnblog)

Android Parcelable

Android 左右滑动加载分页



原文链接: http://blog.csdn.net/sunboy_2050/article/details/7483176

Android 自定义控件

Android 自定义控件

用到的图片文件:

平时都是用简单的控件通过布局来组合一大堆控件,界面复杂起来的话,布局文件就很大,维护起来也很烦。就想把常用的控件组合写成自定义控件,这样维护起来也方便,代码也清爽,下面就以带消除按钮的 EditText 为例。平时,EditText 删除输入的都是按好多下删除键的,看到很多应用的输入框,在输入内容后,会跳出一个清空按钮,点击一下,输入的都会清除,这个用户体验很好。自己尝试了一下,原先是用最简单的控件组合,发现代码量太大,重用性不高,所以想把这个封装成一个自定义控件,直接调用。直接上代码。

    EditTextWithClearButton.java

import xidian.wwf.temperature.R;
import xidian.wwf.temperature.util.StringUtil;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;

/**
 * 带清除按钮的输入框
 * @author WWF
 */
public class EditTextWithClearButton extends LinearLayout{
	
	private EditText editText;
	private ImageButton clearImageButton;

	public EditTextWithClearButton(Context context) {
		super(context);
	}
	
	public EditTextWithClearButton(Context context,AttributeSet attrs){
		super(context, attrs);
		init(context,attrs);
	}
	
	/**
	 * 初始化
	 */
	public void init(Context context,AttributeSet attrs){
		View view = LayoutInflater.from(context).inflate(R.layout.weight_edit_with_clear, thistrue);
		editText = (EditText) view.findViewById(R.id.et);
		clearImageButton = (ImageButton) view.findViewById(R.id.clear_ib);
		clearImageButton.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				editText.setText("");
			}
		});
		editText.addTextChangedListener(new TextWatcher() {
			
			@Override
			public void onTextChanged(CharSequence s, int start, int before, int count) {
				if (s.length() > 0) {
					clearImageButton.setVisibility(View.VISIBLE);
				} else {
					clearImageButton.setVisibility(View.GONE);
				}
			}
			
			@Override
			public void beforeTextChanged(CharSequence s, int start, int count,
					int after) {
				
			}
			@Override
			public void afterTextChanged(Editable s) {
				
			}
		});
		//将属性值设置到控件中
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.EditWithClearText);
		CharSequence hint = a.getText(R.styleable.EditWithClearText_hint);
		CharSequence text = a.getText(R.styleable.EditWithClearText_text);
		if (text!=null&&!StringUtil.isEmpty(text.toString().trim())) {
			editText.setText(text);
			//设置光标位置
			editText.setSelection(text.length());
			this.clearImageButton.setVisibility(View.VISIBLE);
		}else {
			if (hint!=null&&!StringUtil.isEmpty(hint.toString().trim())) {
				editText.setHint(hint);
			}
		}
		a.recycle();
	}
	
	/**
	 * 获得输入的值
	 * @return
	 */
	public String getText(){
		return this.editText.getText().toString();
	}
	
	/**
	 * 设置值
	 * @param text
	 */
	public void setText(String text){
		this.editText.setText(text);
	}
	
	/**
	 * 设置默认值
	 * @param hint
	 */
	public void setHint(String hint){
		this.editText.setHint(hint);
	}
	
	/**
	 * 获得输入框控件
	 * @return
	 */
	public EditText getEditText(){
		return this.editText;
	}
	
	/**
	 * 获得消除按钮
	 * @return
	 */
	public ImageButton getClearImageButton(){
		return this.clearImageButton;
	}
}

weight_edit_with_clear.xml 自定义控件布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@null"
    android:orientation="horizontal" >

    <EditText
        android:id="@+id/et"
        style="@style/text_normal"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@null"
        android:gravity="center_vertical"
        android:maxLength="20"
        android:paddingLeft="5dp"
        android:paddingRight="4dp"
        android:singleLine="true" />

    <ImageButton
        android:id="@+id/clear_ib"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@null"
        android:contentDescription="@string/image_content"
        android:src="@drawable/common_input_box_clear"
        android:visibility="gone" />

</LinearLayout>

这个布局文件就是自定义控件组合的布局文件,我采用线性布局,然后将 EditText 和 ImageButton 组合起来。

属性配置文件 attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="EditTextWithClearBitton">
        <attr name="text"  format="string"/>
        <attr name="hint"  format="string" />
    </declare-styleable>
</resources>


Activity 布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:wwf="http://schemas.android.com/apk/res/xidian.wwf.temperature"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/white"
    android:orientation="vertical" >


    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/table_above_nor"
        android:gravity="center"
        android:orientation="horizontal"
        android:padding="10dp"
        android:layout_margin="10dp" >

        <TextView
            style="@style/text_normal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="10dp"
            android:singleLine="true"
            android:text="@string/account"
            android:textStyle="bold" />

        <xidian.wwf.temperature.weight.EditTextWithClearButton
            android:id="@+id/ssss"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical" 
            wwf:text="@string/system_error"
            >
        </xidian.wwf.temperature.weight.EditTextWithClearButton>
    </LinearLayout>

</LinearLayout>

引入我们自定义的属性,需在布局文件中加入上面的

xmlns:wwf="http://schemas.android.com/apk/res/项目包名"

在控件初始化中,获得属性值,赋给控件即可,赋值的代码

//将属性值设置到控件中
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.EditTextWithClearBitton);
CharSequence hint = a.getText(R.styleable.EditTextWithClearBitton_hint);
CharSequence text = a.getText(R.styleable.EditTextWithClearBitton_text);
if (text!=null&&!StringUtil.isEmpty(text.toString().trim())) {
	editText.setText(text);
	//设置光标位置
	editText.setSelection(text.length());
	this.clearImageButton.setVisibility(View.VISIBLE);
}else {
	if (hint!=null&&!StringUtil.isEmpty(hint.toString().trim())) {
		editText.setHint(hint);
	}
}
a.recycle();

然后就可以使用这个自定义控件了

下面是我运行的结果


android 自定义控件 自定义属性详细介绍

android 自定义控件 自定义属性详细介绍

自定义控件在android中无处不见,自定义控件给了我们很大的方便。比如说,一个视图为imageview ,imagebutton,textview 等诸多控件的组合,用的地方有很多,我们不可能每次都来写3个的组合,既浪费时间,效率又低。在这种情况下,我们就可以自定义一个view来替换他们,不仅提升了效率并且在xml中运用也是相当的美观。
一、控件自定义属性介绍
以下示例中代码均在values/attrs.xml 中定义,属性均可随意命名。
1. reference:参考某一资源ID。
示例:
[java]
复制代码 代码如下:

<declare-styleable name = "名称">
<attr name = "background" format = "reference" />
<attr name = "src" format = "reference" />
</declare-styleable>

2. color:颜色值。
示例:
[java]
复制代码 代码如下:

<declare-styleable name = "名称">
<attr name = "textColor" format = "color" />
</declare-styleable>

3. boolean:布尔值。
示例:
[java]
复制代码 代码如下:

<declare-styleable name = "名称">
<attr name = "focusable" format = "boolean" />
</declare-styleable>

4. dimension:尺寸值。
示例:
[java]
复制代码 代码如下:

<declare-styleable name = "名称">
<attr name = "layout_width" format = "dimension" />
</declare-styleable>

5. float:浮点值。
示例:
[java]
复制代码 代码如下:

<declare-styleable name = "名称">
<attr name = "fromAlpha" format = "float" />
<attr name = "toAlpha" format = "float" />
</declare-styleable>

6. integer:整型值。
示例:
[java]
复制代码 代码如下:

<declare-styleable name = "名称">
<attr name = "frameDuration" format="integer" />
<attr name = "framesCount" format="integer" />
</declare-styleable>

7. string:字符串。
示例:
[java]
复制代码 代码如下:

<declare-styleable name = "名称">
<attr name = "text" format = "string" />
</declare-styleable>

8. fraction:百分数。
示例:
[java]
复制代码 代码如下:

<declare-styleable name="名称">
<attr name = "pivotX" format = "fraction" />
<attr name = "pivotY" format = "fraction" />
</declare-styleable>

9. enum:枚举值。
示例:
[java]
复制代码 代码如下:

<declare-styleable name="名称">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>

10. flag:位或运算。
示例:
[java]
复制代码 代码如下:

<declare-styleable name="名称">
<attr name="windowSoftInputMode">
<flag name = "stateUnspecified" value = "0" />
<flag name = "stateUnchanged" value = "1" />
<flag name = "stateHidden" value = "2" />
<flag name = "statealwaysHidden" value = "3" />
</attr>
</declare-styleable>

11.多类型。
示例:
[java]
复制代码 代码如下:

<declare-styleable name = "名称">
<attr name = "background" format = "reference|color" />
</declare-styleable>

-------------------------------------------------------------------------------------------
二、属性的使用以及自定义控件的实现
1、构思控件的组成元素,思考所需自定义的属性。
比如:我要做一个 <带阴影的按钮,按钮正下方有文字说明>(类似9宫格按钮)
新建values/attrs.xml
[java]
复制代码 代码如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="custom_view">
<attr name="custom_id" format="integer" />
<attr name="src" format="reference" />
<attr name="background" format="reference" />
<attr name="text" format="string" />
<attr name="textColor" format="color" />
<attr name="textSize" format="dimension" />
</declare-styleable>
</resources>

以上,所定义为custom_view,custom_id为按钮id,src为按钮,background为阴影背景,text为按钮说明,textColor为字体颜色,textSize为字体大小。
2、怎么自定义控件呢,怎么使用这些属性呢?话不多说请看代码,CustomView :
复制代码 代码如下:

package com.nanlus.custom;
import com.nanlus.custom.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
public class CustomView extends FrameLayout implements OnClickListener {
private CustomListener customListener = null;
private Drawable mSrc = null,mBackground = null;
private String mText = "";
private int mTextColor = 0;
private float mTextSize = 20;
private int mCustomId = 0;
private ImageView mBackgroundView = null;
private ImageButton mButtonView = null;
private TextView mTextView = null;
private LayoutParams mParams = null;
public CustomView(Context context) {
super(context);
}
public CustomView(Context context,AttributeSet attrs) {
super(context,attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.custom_view);
mSrc = a.getDrawable(R.styleable.custom_view_src);
mBackground = a.getDrawable(R.styleable.custom_view_background);
mText = a.getString(R.styleable.custom_view_text);
mTextColor = a.getColor(R.styleable.custom_view_textColor,
Color.WHITE);
mTextSize = a.getDimension(R.styleable.custom_view_textSize,20);
mCustomId = a.getInt(R.styleable.custom_view_custom_id,0);
mTextView = new TextView(context);
mTextView.setTextSize(mTextSize);
mTextView.setTextColor(mTextColor);
mTextView.setText(mText);
mTextView.setGravity(Gravity.CENTER);
mTextView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
mButtonView = new ImageButton(context);
mButtonView.setimageDrawable(mSrc);
mButtonView.setBackgroundDrawable(null);
mButtonView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
mButtonView.setonClickListener(this);
mBackgroundView = new ImageView(context);
mBackgroundView.setimageDrawable(mBackground);
mBackgroundView.setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
addView(mBackgroundView);
addView(mButtonView);
addView(mTextView);
this.setonClickListener(this);
a.recycle();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mParams = (LayoutParams) mButtonView.getLayoutParams();
if (mParams != null) {
mParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
mButtonView.setLayoutParams(mParams);
}
mParams = (LayoutParams) mBackgroundView.getLayoutParams();
if (mParams != null) {
mParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
mBackgroundView.setLayoutParams(mParams);
}
mParams = (LayoutParams) mTextView.getLayoutParams();
if (mParams != null) {
mParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BottOM;
mTextView.setLayoutParams(mParams);
}
}
public void setCustomListener(CustomListener l) {
customListener = l;
}
@Override
public void onClick(View v) {
if (customListener != null) {
customListener.onCuscomClick(v,mCustomId);
}
}
public interface CustomListener {
void onCuscomClick(View v,int custom_id);
}
}

代码很简单,就不多说,下面来看看我们的CustomView是怎么用的,请看:
3、自定义控件的使用
话不多说,请看代码,main.xml:
[java]
复制代码 代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:nanlus="http://schemas.android.com/apk/res/com.nanlus.custom"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:orientation="horizontal" >
<com.nanlus.custom.CustomView
android:id="@+id/custom1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
nanlus:background="@drawable/background"
nanlus:custom_id="1"
nanlus:src="@drawable/style_button"
nanlus:text="按钮1" >
</com.nanlus.custom.CustomView>
</LinearLayout>
</RelativeLayout>

在这里需要解释一下,
xmlns:nanlus="http://schemas.android.com/apk/res/com.nanlus.custom"
nanlus为在xml中的前缀,com.nanlus.custom为包名
4、在Activity中,直接上代码
[java]
复制代码 代码如下:

package com.nanlus.custom;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.nanlus.BaseActivity;
import com.nanlus.custom.R;
import com.nanlus.custom.CustomView.CustomListener;
public class CustomActivity extends BaseActivity implements CustomListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
((CustomView) this.findViewById(R.id.custom1)).setCustomListener(this);
}
@Override
public void onCuscomClick(View v,int custom_id) {
switch (custom_id) {
case 1:
Toast.makeText(this,"hello !!!",Toast.LENGTH_LONG).show();
break;
default:
break;
}
}
}

Android 自定义控件之自定义属性

Android 自定义控件之自定义属性

前言

自定义控件可以说是 android 里的一个门槛,对很多 android 开发者来说可能都会认为比较难,当然这也是成为一个高手的必经之路,因此我准备在定义控件上多下些功夫,多花点时间研究,多写博客,如果想一起学习自定义控件,欢迎大家关注,如有疑问欢迎留言,如有谬误欢迎批评指正。

目录

通过本篇博客你将学到以下内容

1. 自定义控件的步骤

2. 自定义 view 中自定义属性的方法

3. 自定义属性中 format 10 中类型的详解

一、自定义控件步骤

1. 自定义 View 属性
2. 重写 onMeasure
3. 重写 onLayout
4. 重写 onDraw
今天这篇博客主要介绍第一步自定义属性,通过一个案例来进行讲解
案例的效果如下

即我们通过自定义 View 画一个圆,其中可以控制圆的半径大小和颜色,具体怎么做的呢?






二、自定义属性方法

1. 首先在 res/values/ 目录下创建 attrs 文件,如下所示

<declare-styleable name="CircleView">

      <attr name="circleRadius" format="dimension"/>
      <attr name="circleColor" format="color"/>
</declare-styleable>

WheelRecyclerView 是自己起的名字,不要与系统的冲突就行,
<attr name="circleRadius" format="dimension"/> 其中 name 就是自定义属性的名字(类似于系统控件的 android:layout_width) format 就是属性的类型,这里支持 10 种类型,如下图所示

我们先把这个小案例的流程走完,大家对流程熟悉之后再对细节进行讲解。


2. 编写自定义 View, 并在构造方法中获取我们自定义的属性


   public CircleView(Context context) {
        this(context,null);
    }

    public CircleView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

	/**
     * 获取自定义属性
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

         TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        /**
         * 获取圆的半径属性
         */
        circleRadius=ta.getDimension(R.styleable.CircleView_circleRadius,DEFAULT_RADIUS);
        /**
         * 获取圆的颜色属性
         */
        mColor=ta.getColor(R.styleable.CircleView_circleColor,DEFAULT_COLOR);
        ta.recycle();


        init();
    }

从上面可以看出,在一个参数的构造方法中,调用了两个参数的构造方法,然后在两个参数的构造方法中调用了三个参数的构造方法,然后通过 context 的 obtainStyledAttributes 方法可以得到一个 TypedArray 对象,这个对象里面封装了获取各个属性的方法,比如 getDimension,getColor,getFloat 等方法用来获取属性值。

3. 重写 onMeasure 方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        /**
         * Extracts the mode from the supplied measure specification
         * 从提供的测量规范中获取模式
         */
        int widthMode=MeasureSpec.getMode(widthMeasureSpec);
        /**
         * Extracts the size from the supplied measure specification
         * 从提供的测量规范中获取大小
         */
        int widthSize=MeasureSpec.getSize(widthMeasureSpec);

        int heightMode=MeasureSpec.getMode(heightMeasureSpec);
        int heightSize=MeasureSpec.getSize(heightMeasureSpec);
        /**
         * 如果宽度的测量模式是AT_MOST
         * 则设置宽度
         */
        if(widthMode==MeasureSpec.AT_MOST){

            widthSize= (int) (circleRadius*2+getPaddingLeft()+getPaddingRight());
        }

        if(heightMode==MeasureSpec.AT_MOST){

            heightSize= (int) (circleRadius*2+getPaddingTop()+getPaddingBottom());
        }

        setMeasuredDimension(widthSize,heightSize);
    }

上面我们重写了 onMeasure 方法,获取测量模式、获取控价大小并且按照我们自己的要求制定规则调用 setMeasuredDimension 此方法设置最终的宽和高。

4. 重写 onDraw 方法

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int paddingLeft=getPaddingLeft();
        int paddingTop=getPaddingTop();
        int paddingBottom=getPaddingBottom();
        int paddingRight=getPaddingRight();

        int width=getWidth()-paddingLeft-paddingRight;
        int height=getHeight()-paddingBottom-paddingTop;

        canvas.drawCircle(width/2+paddingLeft,height/2+paddingTop,circleRadius,mPaint);
    }

onDraw 方法很简单就是调用 canvas 的 drawCircle 方法,设置好圆的位置、半径和画笔画一个圆

到这里一个简单的自定义控件以及完成了,在布局中声明我们自定义的 View 如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <com.example.video.CircleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:padding="5dp"
        app:circleRadius="50dp"
        app:circleColor="#FF0000"/>

</LinearLayout>

运行即可看到文章开头的效果。

三、自定义属性 format 类型详解

接下来咱们对自定义属性时的 format 的类型做一下说明,format 的类型一共 10 中分别为:
string、boolean、color、dimension、enum、flags、float、fraction、integer、reference。

1.string

即字符串这个很好理解,类似于系统的 textview 设置 text 内容,在 attrs 中声明如下

<declare-styleable name="名称">
    <attr name="text" format="string"/>
</declare-styleable>

在布局中设置如下

app:text="Hello World!"

系统 textview 的 text 设置如下

android:text="hello world!!!"

2.boolean

布尔类型,有两个值 true 和 false, 类似于系统 Button 的 clickable 属性,在 attrs 中声明如下

<declare-styleable name="名称">
    <attr name="clickable" format="boolean"/>
</declare-styleable>

在布局中设置如下

app:clickable="true"

系统 button 的 clickable 设置如下

android:clickable="false"

3.color

设置颜色即设置 16 进制的颜色,在 attrs 中声明如下

<declare-styleable name="名称">
    <attr name="color" format="color"/>
</declare-styleable>

在布局中有如下两种设置方式

app:color="#FFFFFF"
app:color="@color/id"

系统中给 TextView 设置字体颜色

android:textColor="@color/colorPrimary"

4.dimension

设置尺寸值,一般用来设置 dp 值,在 attrs 中声明如下

<declare-styleable name="名称">
    <attr name="circleRadius" format="dimension" />
</declare-styleable>

在布局中可以进行如下设置

 app:circleRadius="50dp"

系统 textview 设置宽度的方法

 android:layout_width="50dp"

5.enum

设置枚举,一般有几个选项,用来选择,可以参考 LinearLayout 的 orientation 属性,在 attrs 中声明如下

<attr name="orientation">
    <enum name="horizontal" value="0"/>
    <enum name="vertical" value="1"/>
</attr>

在布局中可以进行如下的属性设置

enum

可以看到当我们输入 orientation 的时候会有两个选项就是我们上述 xml 中所声明的 horizontal 和 vertical

系统中的 LinearLayout 与之同理。

6.flags

位或运算即可以进行多个选择,在 atts 中声明如下

<declare-styleable name="CircleView">
     <attr name="gravity" format="flags">
         <flag name="top" value="0"/>
         <flag name="center" value="1"/>
         <flag name="right" value="2"/>
         <flag name="left" value="3"/>
     </attr>
</declare-styleable>

在布局中可以进行如下的属性设置

app:gravity="right|left|top"

7.float

浮点类型,在 attr 中声明如下

<declare-styleable name="CircleView"> 
   <attr name = "alpha" format="float" />
</declare-styleable>

在布局中可以对属性进行如下设置

app:alpha="0.8"

8.fraction

百分数,一般有两种

  • 100% 表示相对于对象自身的百分比
  • 100% p 表示相对于父容器的百分比,percent of parent
<declare-styleable name="CircleView">
   <attr name="progress" format="fraction"/>
</declare-styleable>

在布局中可以属性进行如下设置

app:progress="50%"

9.integer

整形,这个很简单,就不说了

10.reference

某一资源 ID,在 attr 中声明如下

<declare-styleable name="CircleView">
	<attr name="pic" format="reference"/>
</declare-styleable>

在布局中可以对属性进行如下设置

app:pic="@mipmap/ic_launcher"

好了以上就是对自定义属性内容的学习比较简单,但是也必须要从简单的学起,后续会逐步更新自定义 View 相关的内容,如有谬误欢迎批评指正,如果觉得本篇博客对你有帮助,就点个赞呗。

今天关于[android] 切换按钮-自定义控件-拖动效果安卓控件拖动的分享就到这里,希望大家有所收获,若想了解更多关于Android 滑动效果高级篇(八)—— 自定义控件、Android 自定义控件、android 自定义控件 自定义属性详细介绍、Android 自定义控件之自定义属性等相关知识,可以在本站进行查询。

本文标签: