自定义控件,较常用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. <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. }

Android 滑动效果高级篇(八)—— 自定义控件的更多相关文章

  1. Android 滑动效果高级篇(七)—— 华丽翻页效果

    By 何明桂(http://blog.csdn.net/hmg25) 转载请注明出处 之前看到像ipad上的ibook的模拟书籍翻页的特效感觉很炫,在android上也有像laputa和ireader ...

  2. Android 滑动效果进阶篇(六)—— 倒影效果

    上篇介绍了使用Animation实现3D动画旋转翻页效果,现在介绍图片倒影实现,先看效果图 本示例主要通过自定义Gallery和ImageAdapter(继承自BaseAdapter)实现 1.倒影绘 ...

  3. Android 滑动效果入门篇(二)—— Gallery

    Gallery 是Android官方提供的一个View容器类,继承于AbsSpinner类,用于实现页面滑动效果. 从上面的继承关系可以看出,AbsSpinner类继承自AdapterView,因此我 ...

  4. Android 滑动效果入门篇(一)—— ViewFlipper

    ViewFilpper 是Android官方提供的一个View容器类,继承于ViewAnimator类,用于实现页面切换,也可以设定时间间隔,让它自动播放.又ViewAnimator继承至于Frame ...

  5. Android 滑动效果进阶篇(五)—— 3D旋转

    前面介绍了利用Android自带的控件,进行滑动翻页制作效果,现在我们通过代码实现一些滑动翻页的动画效果. Animation实现动画有两个方式:帧动画(frame-by-frame animatio ...

  6. Android 滑动效果基础篇(四)—— Gallery + GridView

    Android系统自带一个GridView和Gallery两个控件,GridView网格显示,Gallery单个浏览,两者结合起来可以真正实现Gallery浏览图片效果. 本示例通过GridView和 ...

  7. Android 滑动效果基础篇(三)—— Gallery仿图像集浏览

    Android系统自带一个Gallery浏览图片的应用,通过手指拖动时能够非常流畅的显示图片,用户交互和体验都很好. 本示例就是通过Gallery和自定义的View,模仿实现一个仿Gallery图像集 ...

  8. 十六、Android 滑动效果汇总

    Android 滑动效果入门篇(一)—— ViewFlipper Android 滑动效果入门篇(二)—— Gallery Android 滑动效果基础篇(三)—— Gallery仿图像集浏览 And ...

  9. Android 滑动效果汇总

    Android 滑动效果入门篇(一)—— ViewFlipper Android 滑动效果入门篇(二)—— Gallery Android 滑动效果基础篇(三)—— Gallery仿图像集浏览 And ...

随机推荐

  1. C# chart,有关如何在鼠标移动到Series上时显示节点及数据 (有待继续更新)

    一.效果与思路 效果: 解决方案1 用chart的mousemove时间,实时跟踪鼠标最近的X轴的位置,然后把cursorX设置到那个位置上,让用户知道我是选的那一个X的值,同时用tooltip显示该 ...

  2. 回调函数、Java接口回调 总结

    谈到回调,我们得先从回调函数说起,什么叫回调函数呢? 回调函数是什么? 百度百科的解释:回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用 ...

  3. 自学hadoop(三)

    1) 关于hadoop在eclipse插件.经过自己的摸爬滚打.总结一下三条.     a) 2.0或者0.23.0吧 google比较方便.其他的可以自己编译.(这个我不敢保证.我本地环境事2.1. ...

  4. JAVA中的异常(异常处理流程、异常处理的缺陷)

    异常处理流程 1)首先由try{...}catch(Exception e){ System.out.println(e); e.printStackTrace(); }finally{...}结构 ...

  5. Java中Runnable和Thread的区别(转)

    http://developer.51cto.com/art/201203/321042.htm 第一种方式:使用Runnable接口创建线程 第二种方式:直接继承Thread类创建对象 使用Runn ...

  6. 设置mysql 在mac中的环境变量

    在mac os的用户目录下有一个隐藏文件.bash_profile,编辑它就可以完成环境变量的创建. 比如要将mysql的运行目录加到环境变量中,可以在.bash_profile中新增如下一行: ex ...

  7. No module named BeautifulSoup

    遇到 No module named BeautifulSoup 错误,但是的确从官方下载了BeautifulSoup,并安装成功. 后来才发现,有两个BeautifulSoup的版本,一个是2012 ...

  8. iOS本地数据存取

    应用沙盒 1)每个iOS应用都有自己的应用沙盒(应用沙盒就是文件系统目录),与其他文件系统隔离.应用必须待在自己的沙盒里,其他应用不能访问该沙盒 2)应用沙盒的文件系统目录,如下图所示(假设应用的名称 ...

  9. FIREDAC调用中间件远程方法查询数据示例

    服务端使用FDQUERY查询数据并返回TDATASET: function TServerMethods1.GetData(var sql: string): tdataset;begin qry.C ...

  10. Python多线程学习资料1

    一.Python中的线程使用: Python中使用线程有两种方式:函数或者用类来包装线程对象. 1.  函数式:调用thread模块中的start_new_thread()函数来产生新线程.如下例: ...