首页 技术 正文
技术 2022年11月7日
0 收藏 344 点赞 901 浏览 13365 个字

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

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

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

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

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

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. <declare-styleable name=”com.myapps.widget.PagerBar”>
  7. <attr name=”barColor” format=”color” />
  8. <attr name=”highlightColor” format=”color” />
  9. <attr name=”fadeDelay” format=”integer” />
  10. <attr name=”fadeDuration” format=”integer” />
  11. <attr name=”roundRectRadius” format=”dimension” />
  12. </declare-styleable>
  13. </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();

最后,在布局文件中设置属性,如在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” />  <!– 自定义圆角 –>

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

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. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_Pager);
  5. pageWidthSpec = a.getDimensionPixelSize(R.styleable.com_myapps_widget_Pager_pageWidth, SPEC_UNDEFINED);
  6. a.recycle();
  7. }

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. int getCurrentPage() {
  7. return mCurrentPage;
  8. }
  9. public void setPageWidth(int pageWidth) {
  10. this.pageWidthSpec = pageWidth;
  11. }
  12. public int getPageWidth() {
  13. return pageWidth;
  14. }
  15. /** 获取whichPage的Pager起始x位置,whichPage从0开始计 */
  16. private int getScrollXForPage(int whichPage) {
  17. return (whichPage * pageWidth) – pageWidthPadding();
  18. }
  19. /** 返回View的 paddingwidth 一半(1/2)*/
  20. int pageWidthPadding() {
  21. return ((getMeasuredWidth() – pageWidth) / 2);
  22. }
  23. @Override
  24. public void computeScroll() {       // update  mScrollX and mScrollY  of View
  25. if (mScroller.computeScrollOffset()) {
  26. scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  27. postInvalidate();           // invalidate the View from a non-UI thread
  28. } else if (mNextPage != INVALID_SCREEN) {
  29. mCurrentPage = mNextPage;
  30. mNextPage = INVALID_SCREEN;
  31. clearChildrenCache();
  32. }
  33. }
  34. @Override
  35. protected void dispatchDraw(Canvas canvas) {    // draw the child views
  36. final long drawingTime = getDrawingTime();  // 绘制childView
  37. final int count = getChildCount();
  38. for (int i = 0; i < count; i++) {
  39. drawChild(canvas, getChildAt(i), drawingTime);
  40. }
  41. for (OnScrollListener mListener : mListeners) { // 自定义接口
  42. int adjustedScrollX = getScrollX() + pageWidthPadding();
  43. mListener.onScroll(adjustedScrollX);
  44. if (adjustedScrollX % pageWidth == 0) { // scroll finished
  45. mListener.onViewScrollFinished(adjustedScrollX / pageWidth);
  46. }
  47. }
  48. }
  49. @Override
  50. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  51. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  52. pageWidth = (pageWidthSpec == SPEC_UNDEFINED) ? getMeasuredWidth() : pageWidthSpec;
  53. pageWidth = Math.min(pageWidth, getMeasuredWidth());
  54. final int count = getChildCount();
  55. for (int i = 0; i < count; i++) {
  56. widthMeasureSpec = MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY);
  57. getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
  58. }
  59. if (mFirstLayout) { // 第一次显示Pager时,page的位置
  60. scrollTo(getScrollXForPage(mCurrentPage), mScroller.getCurrY());
  61. mFirstLayout = false;
  62. }
  63. }
  64. @Override
  65. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  66. int childLeft = 0;
  67. final int count = getChildCount();  // 绘制childView
  68. for (int i = 0; i < count; i++) {
  69. final View child = getChildAt(i);
  70. if (child.getVisibility() != View.GONE) {
  71. final int childWidth = child.getMeasuredWidth();
  72. child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
  73. childLeft += childWidth;
  74. }
  75. }
  76. }
  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. final float x = ev.getX();
  8. final float y = ev.getY();
  9. switch (action) {
  10. case MotionEvent.ACTION_MOVE:
  11. if (mTouchState == TOUCH_STATE_REST) {
  12. checkStartScroll(x, y);
  13. }
  14. break;
  15. case MotionEvent.ACTION_DOWN:
  16. mLastMotionX = x;
  17. mLastMotionY = y;
  18. mAllowLongPress = true;
  19. mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;    // scroll 完成后,重置状态
  20. break;
  21. case MotionEvent.ACTION_CANCEL:
  22. case MotionEvent.ACTION_UP:
  23. clearChildrenCache();
  24. mTouchState = TOUCH_STATE_REST;
  25. break;
  26. }
  27. return mTouchState != TOUCH_STATE_REST;
  28. }
  29. @Override
  30. public boolean onTouchEvent(MotionEvent ev) {
  31. if (mVelocityTracker == null) {
  32. mVelocityTracker = VelocityTracker.obtain();
  33. }
  34. mVelocityTracker.addMovement(ev);
  35. final int action = ev.getAction();
  36. final float x = ev.getX();
  37. final float y = ev.getY();
  38. switch (action) {
  39. case MotionEvent.ACTION_DOWN:
  40. if (!mScroller.isFinished()) {
  41. mScroller.abortAnimation();
  42. }
  43. mLastMotionX = x;
  44. break;
  45. case MotionEvent.ACTION_MOVE:
  46. if (mTouchState == TOUCH_STATE_REST) {
  47. checkStartScroll(x, y);
  48. } else if (mTouchState == TOUCH_STATE_SCROLLING) {  // scrolling 状态时,重绘view
  49. int deltaX = (int) (mLastMotionX – x);
  50. mLastMotionX = x;
  51. if (getScrollX() < 0 || getScrollX() > getChildAt(getChildCount() – 1).getLeft()) {
  52. deltaX /= 2;
  53. }
  54. scrollBy(deltaX, 0);
  55. }
  56. break;
  57. case MotionEvent.ACTION_UP:
  58. if (mTouchState == TOUCH_STATE_SCROLLING) {
  59. final VelocityTracker velocityTracker = mVelocityTracker;
  60. velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
  61. int velocityX = (int) velocityTracker.getXVelocity();
  62. if (velocityX > SNAP_VELOCITY && mCurrentPage > 0) {
  63. snapToPage(mCurrentPage – 1);
  64. } else if (velocityX < -SNAP_VELOCITY && mCurrentPage < getChildCount() – 1) {
  65. snapToPage(mCurrentPage + 1);
  66. } else {
  67. snapToDestination();
  68. }
  69. if (mVelocityTracker != null) {
  70. mVelocityTracker.recycle();
  71. mVelocityTracker = null;
  72. }
  73. }
  74. mTouchState = TOUCH_STATE_REST;
  75. break;
  76. case MotionEvent.ACTION_CANCEL:
  77. mTouchState = TOUCH_STATE_REST;
  78. }
  79. return true;
  80. }
  81. /** 检查scroll状态,并设置绘制scroll缓存 */
  82. private void checkStartScroll(float x, float y) {
  83. final int xDiff = (int) Math.abs(x – mLastMotionX);
  84. final int yDiff = (int) Math.abs(y – mLastMotionY);
  85. boolean xMoved = xDiff > mTouchSlop;
  86. boolean yMoved = yDiff > mTouchSlop;
  87. if (xMoved || yMoved) {
  88. if (xMoved) {
  89. mTouchState = TOUCH_STATE_SCROLLING;        // 设置为scrolling 状态
  90. enableChildrenCache();
  91. }
  92. if (mAllowLongPress) {
  93. mAllowLongPress = false;
  94. final View currentScreen = getChildAt(mCurrentPage);
  95. currentScreen.cancelLongPress();    // Cancels a pending long press
  96. }
  97. }
  98. }
  99. void enableChildrenCache() {
  100. setChildrenDrawingCacheEnabled(true);       // Enables or disables the drawing cache for each child of this viewGroup
  101. setChildrenDrawnWithCacheEnabled(true); // Tells the ViewGroup to draw its children using their drawing cache
  102. }
  103. void clearChildrenCache() {
  104. setChildrenDrawnWithCacheEnabled(false);
  105. }
  106. private void snapToDestination() {
  107. final int startX = getScrollXForPage(mCurrentPage);
  108. int whichPage = mCurrentPage;
  109. if (getScrollX() < startX – getWidth() / 8) {
  110. whichPage = Math.max(0, whichPage – 1);
  111. } else if (getScrollX() > startX + getWidth() / 8) {
  112. whichPage = Math.min(getChildCount() – 1, whichPage + 1);
  113. }
  114. snapToPage(whichPage);
  115. }
  116. void snapToPage(int whichPage) {
  117. enableChildrenCache();
  118. boolean changingPages = whichPage != mCurrentPage;
  119. mNextPage = whichPage;
  120. View focusedChild = getFocusedChild();
  121. if (focusedChild != null && changingPages && focusedChild == getChildAt(mCurrentPage)) {
  122. focusedChild.clearFocus();
  123. }
  124. final int newX = getScrollXForPage(whichPage);
  125. final int delta = newX – getScrollX();
  126. mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
  127. invalidate();
  128. }
  129. /** 向左滑动 */
  130. public void scrollLeft() {
  131. if (mNextPage == INVALID_SCREEN && mCurrentPage > 0 && mScroller.isFinished()) {
  132. snapToPage(mCurrentPage – 1);
  133. }
  134. }
  135. /** 向右滑动 */
  136. public void scrollRight() {
  137. if (mNextPage == INVALID_SCREEN && mCurrentPage < getChildCount() – 1 && mScroller.isFinished()) {
  138. snapToPage(mCurrentPage + 1);
  139. }
  140. }

C、容器状态保存(onSaveInstanceState)

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

D、容器事件监听接口

    1. public void addOnScrollListener(OnScrollListener listener) {
    2. mListeners.add(listener);
    3. }
    4. public void removeOnScrollListener(OnScrollListener listener) {
    5. mListeners.remove(listener);
    6. }
    7. /** 自定义接口 */
    8. public static interface OnScrollListener {
    9. void onScroll(int scrollX);
    10. void onViewScrollFinished(int currentPage);
    11. }
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,085
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,560
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,409
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,182
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,819
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,902