android仿iPhone滚轮控件实现及源码分析

首先,先看下效果图:

1332813916_4919.jpg

1332813930_8607.jpg 1332813939_8118.jpg

      这三张图分别是使用滚动控件实现城市,随机数和时间三个简单的例子,当然,界面有点简陋,下面我们就以时间这个为例,开始解析一下。

     首先,先看下布局文件:


  1. <?xml version="1.0" encoding="utf-8"?>

  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.         android:layout_height="wrap_content"
  4.         android:layout_width="fill_parent"
  5.         android:layout_marginTop="12dp"
  6.         android:orientation="vertical"
  7.         android:background="@drawable/layout_bg">
  8.         
  9.         <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  10.                 android:layout_height="wrap_content"
  11.                 android:layout_width="fill_parent"
  12.                 android:layout_gravity="center_horizontal"
  13.                 android:paddingLeft="12dp"
  14.                 android:paddingRight="12dp"
  15.                 android:paddingTop="10dp">
  16.          
  17.                   <kankan.wheel.widget.WheelView android:id="@+id/hour"
  18.                         android:layout_height="wrap_content"
  19.                         android:layout_width="fill_parent"
  20.                         android:layout_weight="1"/>
  21.                 <kankan.wheel.widget.WheelView android:id="@+id/mins"
  22.                         android:layout_height="wrap_content"
  23.                         android:layout_width="fill_parent"
  24.                         android:layout_weight="1"/>
  25.         </LinearLayout>
  26.         
  27.         <TimePicker android:id="@+id/time"
  28.                 android:layout_marginTop="12dp"
  29.                 android:layout_height="wrap_content"
  30.                 android:layout_width="fill_parent"
  31.                 android:layout_weight="1"/>
  32.                
  33. </LinearLayout>
复制代码
里面只有三个控件,两个自定义的WheelView,还有一个TimePicker,然后进入代码里面看一下:
  1. public class TimeActivity extends Activity {
  2.         // Time changed flag
  3.         private boolean timeChanged = false;
  4.         
  5.         //
  6.         private boolean timeScrolled = false;
  7.         
  8.         @Override
  9.         public void onCreate(Bundle savedInstanceState) {
  10.                 super.onCreate(savedInstanceState);

  11.                 setContentView(R.layout.time_layout);
  12.         
  13.                 final WheelView hours = (WheelView) findViewById(R.id.hour);
  14.                 hours.setAdapter(new NumericWheelAdapter(0, 23));
  15.                 hours.setLabel("hours");
  16.         
  17.                 final WheelView mins = (WheelView) findViewById(R.id.mins);
  18.                 mins.setAdapter(new NumericWheelAdapter(0, 59, "%02d"));
  19.                 mins.setLabel("mins");
  20.                 mins.setCyclic(true);
  21.         
  22.                 final TimePicker picker = (TimePicker) findViewById(R.id.time);
  23.                 picker.setIs24HourView(true);
  24.         
  25.                 // set current time
  26.                 Calendar c = Calendar.getInstance();
  27.                 int curHours = c.get(Calendar.HOUR_OF_DAY);
  28.                 int curMinutes = c.get(Calendar.MINUTE);
  29.         
  30.                 hours.setCurrentItem(curHours);
  31.                 mins.setCurrentItem(curMinutes);
  32.         
  33.                 picker.setCurrentHour(curHours);
  34.                 picker.setCurrentMinute(curMinutes);
  35.         
  36.                 // add listeners
  37.                 addChangingListener(mins, "min");
  38.                 addChangingListener(hours, "hour");
  39.         
  40.                 OnWheelChangedListener wheelListener = new OnWheelChangedListener() {
  41.                         public void onChanged(WheelView wheel, int oldValue, int newValue) {
  42.                                 if (!timeScrolled) {
  43.                                         timeChanged = true;
  44.                                         picker.setCurrentHour(hours.getCurrentItem());
  45.                                         picker.setCurrentMinute(mins.getCurrentItem());
  46.                                         timeChanged = false;
  47.                                 }
  48.                         }
  49.                 };

  50.                 hours.addChangingListener(wheelListener);
  51.                 mins.addChangingListener(wheelListener);

  52.                 OnWheelScrollListener scrollListener = new OnWheelScrollListener() {
  53.                         public void onScrollingStarted(WheelView wheel) {
  54.                                 timeScrolled = true;
  55.                         }
  56.                         public void onScrollingFinished(WheelView wheel) {
  57.                                 timeScrolled = false;
  58.                                 timeChanged = true;
  59.                                 picker.setCurrentHour(hours.getCurrentItem());
  60.                                 picker.setCurrentMinute(mins.getCurrentItem());
  61.                                 timeChanged = false;
  62.                         }
  63.                 };
  64.                
  65.                 hours.addScrollingListener(scrollListener);
  66.                 mins.addScrollingListener(scrollListener);
  67.                
  68.                 picker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
  69.                         public void onTimeChanged(TimePicker  view, int hourOfDay, int minute) {
  70.                                 if (!timeChanged) {
  71.                                         hours.setCurrentItem(hourOfDay, true);
  72.                                         mins.setCurrentItem(minute, true);
  73.                                 }
  74.                         }
  75.                 });
  76.         }

  77.         /**
  78.          * Adds changing listener for wheel that updates the wheel label
  79.          * @param wheel the wheel
  80.          * @param label the wheel label
  81.          */
  82.         private void addChangingListener(final WheelView wheel, final String label) {
  83.                 wheel.addChangingListener(new OnWheelChangedListener() {
  84.                         public void onChanged(WheelView wheel, int oldValue, int newValue) {
  85.                                 wheel.setLabel(newValue != 1 ? label + "s" : label);
  86.                         }
  87.                 });
  88.         }
  89. }
复制代码
看一下,里面调用WheelView的方法有
setAdapter()、
setLabel("mins")、
setCyclic(true)、setCurrentItem()、getCurrentItem()、addChangingListener()、addScrollingListener()这些方法,其中setAapter设置数据适配器,setCyclic()设置是否是循环,setCurrentItem和getCurrentItem分别是设置现在选择的item和返回现在选择的item。后面两个设置监听的方法中,需要重写两个接口:

  1. /**
  2. * Wheel scrolled listener interface.
  3. */
  4. public interface OnWheelScrollListener {
  5.         /**
  6.          * Callback method to be invoked when scrolling started.
  7.          * @param wheel the wheel view whose state has changed.
  8.          */
  9.         void onScrollingStarted(WheelView wheel);
  10.         
  11.         /**
  12.          * Callback method to be invoked when scrolling ended.
  13.          * @param wheel the wheel view whose state has changed.
  14.          */
  15.         void onScrollingFinished(WheelView wheel);
  16. }
复制代码

  1. public interface OnWheelChangedListener {
  2.         /**
  3.          * Callback method to be invoked when current item changed
  4.          * @param wheel the wheel view whose state has changed
  5.          * @param oldValue the old value of current item
  6.          * @param newValue the new value of current item
  7.          */
  8.         void onChanged(WheelView wheel, int oldValue, int newValue);
  9. }
复制代码
在这里使用的是典型的回调方法模式。

然后现在,我们进入WheelView类,看一下他是如何构建,首先,WheelView继承了View类。代码的22行到45行是导入的所需要的类。从54行到135行是声明一些变量和类:

  1. /** Scrolling duration */
  2.         private static final int SCROLLING_DURATION = 400;

  3.         /** Minimum delta for scrolling */
  4.         private static final int MIN_DELTA_FOR_SCROLLING = 1;

  5.         /** Current value & label text color */
  6.         private static final int VALUE_TEXT_COLOR = 0xF0000000;

  7.         /** Items text color */
  8.         private static final int ITEMS_TEXT_COLOR = 0xFF000000;

  9.         /** Top and bottom shadows colors */
  10.         private static final int[] SHADOWS_COLORS = new int[] { 0xFF111111,
  11.                         0x00AAAAAA, 0x00AAAAAA };

  12.         /** Additional items height (is added to standard text item height) */
  13.         private static final int ADDITIONAL_ITEM_HEIGHT = 15;

  14.         /** Text size */
  15.         private static final int TEXT_SIZE = 24;

  16.         /** Top and bottom items offset (to hide that) */
  17.         private static final int ITEM_OFFSET = TEXT_SIZE / 5;

  18.         /** Additional width for items layout */
  19.         private static final int ADDITIONAL_ITEMS_SPACE = 10;

  20.         /** Label offset */
  21.         private static final int LABEL_OFFSET = 8;

  22.         /** Left and right padding value */
  23.         private static final int PADDING = 10;

  24.         /** Default count of visible items */
  25.         private static final int DEF_VISIBLE_ITEMS = 5;

  26.         // Wheel Values
  27.         private WheelAdapter adapter = null;
  28.         private int currentItem = 0;
  29.         
  30.         // Widths
  31.         private int itemsWidth = 0;
  32.         private int labelWidth = 0;

  33.         // Count of visible items
  34.         private int visibleItems = DEF_VISIBLE_ITEMS;
  35.         
  36.         // Item height
  37.         private int itemHeight = 0;

  38.         // Text paints
  39.         private TextPaint itemsPaint;
  40.         private TextPaint valuePaint;

  41.         // Layouts
  42.         private StaticLayout itemsLayout;
  43.         private StaticLayout labelLayout;
  44.         private StaticLayout valueLayout;

  45.         // Label & background
  46.         private String label;
  47.         private Drawable centerDrawable;

  48.         // Shadows drawables
  49.         private GradientDrawable topShadow;
  50.         private GradientDrawable bottomShadow;

  51.         // Scrolling
  52.         private boolean isScrollingPerformed;
  53.         private int scrollingOffset;

  54.         // Scrolling animation
  55.         private GestureDetector gestureDetector;
  56.         private Scroller scroller;
  57.         private int lastScrollY;

  58.         // Cyclic
  59.         boolean isCyclic = false;
  60.         
  61.         // Listeners
  62.         private List<OnWheelChangedListener> changingListeners = new LinkedList<OnWheelChangedListener>();
  63.         private List<OnWheelScrollListener> scrollingListeners = new LinkedList<OnWheelScrollListener>();
复制代码
  在这里面,使用到了StaticLayout,在开发文档中找一下这个类:

  1. StaticLayout is a Layout for text that will not be edited after it is laid out. Use DynamicLayout for text that may change.

  2. This is used by widgets to control text layout. You should not need to use this class directly unless you are implementing your own widget or custom display object, or would be tempted to call Canvas.drawText() directly.
复制代码
staticLayout被创建以后就不能被修改了,通常被用于控制文本组件布局。    还使用到了Drawable、Text'Paint、GradientDrawable、GestureDetector、Scroller类,在开发文档中,GradientDrawable的概述:
  1. A Drawable with a color gradient for buttons, backgrounds, etc.

  2. It can be defined in an XML file with the <shape> element. For more information, see the guide to Drawable Resources.
复制代码
就是说这个类可以为按钮或者背景等提供渐变颜色的绘制。TextPaint的概述:
  1. TextPaint is an extension of Paint that leaves room for some extra data used during text measuring and drawing.
复制代码
TextPaint是Paint类的一个扩展,主要是用于文本在绘制的过程中为附件的数据留出空间。
GestureDetector:手势检测,看下开发文档中关于该类的概述:
  1. Detects various gestures and events using the supplied MotionEvents. The GestureDetector.OnGestureListener callback will notify users when a particular motion event has occurred. This class should only be used with MotionEvents reported via touch (don't use for trackball events).
复制代码
  为各种手势和事件提供MotionEvents。当一个具体的事件发生时会调用回调函数GestureDetector.OnGestureListener。这个类应该只适用于MotionEvents通过触摸触发的事件(不要使用追踪事件)。

        140行到156行是构造方法,175到183行是set和getAdapter。在193行,setInterpolator()方法,设置interPolator这个动画接口,我们看下这个接口的概述:
  1. An interpolator defines the rate of change of an animation. This allows the basic animation effects (alpha, scale, translate, rotate) to be accelerated, decelerated, repeated, etc.
复制代码
定义了一种基于变率的一个动画。这使得基本的动画效果(alpha, scale, translate, rotate )是加速,减慢,重复等。这个方法在随机数这个例子中被使用。
        203行到213行设置显示的item条数。在setVisibleItems()方法里面调用了View的invalidate()方法,看下文档中对该方法的介绍:
  1. Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future. This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().
复制代码
使全部视图失效,如果View视图是可见的,会在UI线程里面从新调用onDraw()方法。       223行到233行是设置Label,既后面图片中的hours. 1332829572_5658.jpg
2012-3-31 08:44 上传
下载附件(3.15 KB)
      


245行到296行是设置监听,在上面已经简单的说了一下,这里不在累述。



       307行到349行是设置正被选中item,就是在那个阴影条框下的那个部分,比较简单。里面主要调用了scroll这个方法:
  1. /**
  2.          * Scroll the wheel
  3.          * @param itemsToSkip items to scroll
  4.          * @param time scrolling duration
  5.          */
  6.         public void scroll(int itemsToScroll, int time) {
  7.                 scroller.forceFinished(true);
  8.                 lastScrollY = scrollingOffset;
  9.                 int offset = itemsToScroll * getItemHeight();               
  10.                 scroller.startScroll(0, lastScrollY, 0, offset - lastScrollY, time);
  11.                 setNextMessage(MESSAGE_SCROLL);               
  12.                 startScrolling();
  13.         }
复制代码
357行到365行是设置item数据能否循环使用。


       384行的initResourcesIfNecessary()方法,从字面意思,如果需要的初始化资源。
  1. private void initResourcesIfNecessary() {
  2.                 if (itemsPaint == null) {
  3.                         itemsPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG
  4.                                         | Paint.FAKE_BOLD_TEXT_FLAG);
  5.                         //itemsPaint.density = getResources().getDisplayMetrics().density;
  6.                         itemsPaint.setTextSize(TEXT_SIZE);
  7.                 }

  8.                 if (valuePaint == null) {
  9.                         valuePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG
  10.                                         | Paint.FAKE_BOLD_TEXT_FLAG | Paint.DITHER_FLAG);
  11.                         //valuePaint.density = getResources().getDisplayMetrics().density;
  12.                         valuePaint.setTextSize(TEXT_SIZE);
  13.                         valuePaint.setShadowLayer(0.1f, 0, 0.1f, 0xFFC0C0C0);
  14.                 }

  15.                 if (centerDrawable == null) {
  16.                         centerDrawable = getContext().getResources().getDrawable(R.drawable.wheel_val);
  17.                 }

  18.                 if (topShadow == null) {
  19.                         topShadow = new GradientDrawable(Orientation.TOP_BOTTOM, SHADOWS_COLORS);
  20.                 }

  21.                 if (bottomShadow == null) {
  22.                         bottomShadow = new GradientDrawable(Orientation.BOTTOM_TOP, SHADOWS_COLORS);
  23.                 }

  24.                 setBackgroundResource(R.drawable.wheel_bg);
  25.         }
复制代码

这个方法就是初始化在532行calculateLayoutWidth()方法中调用了这个方法,同时调用了487行的getMaxTextLength()这个方法。
      471行getTextItem(int index)通过一个索引获取该item的文本。

      这是第一部分,没有多少有太多意思的地方,重点的地方在以后532行到940行的内容,另起一篇,开始分析,这一篇先到这。


第二部分,大家往下看!

在上一篇。简单的说了下架构还有效果图,但是关于图形的绘制各方面的代码在532行到940行,如果写在一篇文章里面,可能会导致文章太长,效果不好,所以自作聪明的分成了两篇。闲言碎语不要讲,下面开始正事。

       首先,先把代码贴出来:

  1. /**
  2.          * Calculates control width and creates text layouts
  3.          * @param widthSize the input layout width
  4.          * @param mode the layout mode
  5.          * @return the calculated control width
  6.          */
  7.         private int calculateLayoutWidth(int widthSize, int mode) {
  8.                 initResourcesIfNecessary();

  9.                 int width = widthSize;
  10.                 int maxLength = getMaxTextLength();
  11.                 if (maxLength > 0) {
  12.                         float textWidth = FloatMath.ceil(Layout.getDesiredWidth("0", itemsPaint));
  13.                         itemsWidth = (int) (maxLength * textWidth);
  14.                 } else {
  15.                         itemsWidth = 0;
  16.                 }
  17.                 itemsWidth += ADDITIONAL_ITEMS_SPACE; // make it some more

  18.                 labelWidth = 0;
  19.                 if (label != null && label.length() > 0) {
  20.                         labelWidth = (int) FloatMath.ceil(Layout.getDesiredWidth(label, valuePaint));
  21.                 }

  22.                 boolean recalculate = false;
  23.                 if (mode == MeasureSpec.EXACTLY) {
  24.                         width = widthSize;
  25.                         recalculate = true;
  26.                 } else {
  27.                         width = itemsWidth + labelWidth + 2 * PADDING;
  28.                         if (labelWidth > 0) {
  29.                                 width += LABEL_OFFSET;
  30.                         }

  31.                         // Check against our minimum width
  32.                         width = Math.max(width, getSuggestedMinimumWidth());

  33.                         if (mode == MeasureSpec.AT_MOST && widthSize < width) {
  34.                                 width = widthSize;
  35.                                 recalculate = true;
  36.                         }
  37.                 }

  38.                 if (recalculate) {
  39.                         // recalculate width
  40.                         int pureWidth = width - LABEL_OFFSET - 2 * PADDING;
  41.                         if (pureWidth <= 0) {
  42.                                 itemsWidth = labelWidth = 0;
  43.                         }
  44.                         if (labelWidth > 0) {
  45.                                 double newWidthItems = (double) itemsWidth * pureWidth
  46.                                                 / (itemsWidth + labelWidth);
  47.                                 itemsWidth = (int) newWidthItems;
  48.                                 labelWidth = pureWidth - itemsWidth;
  49.                         } else {
  50.                                 itemsWidth = pureWidth + LABEL_OFFSET; // no label
  51.                         }
  52.                 }

  53.                 if (itemsWidth > 0) {
  54.                         createLayouts(itemsWidth, labelWidth);
  55.                 }

  56.                 return width;
  57.         }

  58.         /**
  59.          * Creates layouts
  60.          * @param widthItems width of items layout
  61.          * @param widthLabel width of label layout
  62.          */
  63.         private void createLayouts(int widthItems, int widthLabel) {
  64.                 if (itemsLayout == null || itemsLayout.getWidth() > widthItems) {
  65.                         itemsLayout = new StaticLayout(buildText(isScrollingPerformed), itemsPaint, widthItems,
  66.                                         widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,
  67.                                         1, ADDITIONAL_ITEM_HEIGHT, false);
  68.                 } else {
  69.                         itemsLayout.increaseWidthTo(widthItems);
  70.                 }

  71.                 if (!isScrollingPerformed && (valueLayout == null || valueLayout.getWidth() > widthItems)) {
  72.                         String text = getAdapter() != null ? getAdapter().getItem(currentItem) : null;
  73.                         valueLayout = new StaticLayout(text != null ? text : "",
  74.                                         valuePaint, widthItems, widthLabel > 0 ?
  75.                                                         Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,
  76.                                                         1, ADDITIONAL_ITEM_HEIGHT, false);
  77.                 } else if (isScrollingPerformed) {
  78.                         valueLayout = null;
  79.                 } else {
  80.                         valueLayout.increaseWidthTo(widthItems);
  81.                 }

  82.                 if (widthLabel > 0) {
  83.                         if (labelLayout == null || labelLayout.getWidth() > widthLabel) {
  84.                                 labelLayout = new StaticLayout(label, valuePaint,
  85.                                                 widthLabel, Layout.Alignment.ALIGN_NORMAL, 1,
  86.                                                 ADDITIONAL_ITEM_HEIGHT, false);
  87.                         } else {
  88.                                 labelLayout.increaseWidthTo(widthLabel);
  89.                         }
  90.                 }
  91.         }

  92.         @Override
  93.         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  94.                 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  95.                 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  96.                 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  97.                 int heightSize = MeasureSpec.getSize(heightMeasureSpec);

  98.                 int width = calculateLayoutWidth(widthSize, widthMode);

  99.                 int height;
  100.                 if (heightMode == MeasureSpec.EXACTLY) {
  101.                         height = heightSize;
  102.                 } else {
  103.                         height = getDesiredHeight(itemsLayout);

  104.                         if (heightMode == MeasureSpec.AT_MOST) {
  105.                                 height = Math.min(height, heightSize);
  106.                         }
  107.                 }

  108.                 setMeasuredDimension(width, height);
  109.         }

  110.         @Override
  111.         protected void onDraw(Canvas canvas) {
  112.                 super.onDraw(canvas);
  113.                
  114.                 if (itemsLayout == null) {
  115.                         if (itemsWidth == 0) {
  116.                                 calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);
  117.                         } else {
  118.                                 createLayouts(itemsWidth, labelWidth);
  119.                         }
  120.                 }

  121.                 if (itemsWidth > 0) {
  122.                         canvas.save();
  123.                         // Skip padding space and hide a part of top and bottom items
  124.                         canvas.translate(PADDING, -ITEM_OFFSET);
  125.                         drawItems(canvas);
  126.                         drawValue(canvas);
  127.                         canvas.restore();
  128.                 }

  129.                 drawCenterRect(canvas);
  130.                 drawShadows(canvas);
  131.         }

  132.         /**
  133.          * Draws shadows on top and bottom of control
  134.          * @param canvas the canvas for drawing
  135.          */
  136.         private void drawShadows(Canvas canvas) {
  137.                 topShadow.setBounds(0, 0, getWidth(), getHeight() / visibleItems);
  138.                 topShadow.draw(canvas);

  139.                 bottomShadow.setBounds(0, getHeight() - getHeight() / visibleItems,
  140.                                 getWidth(), getHeight());
  141.                 bottomShadow.draw(canvas);
  142.         }

  143.         /**
  144.          * Draws value and label layout
  145.          * @param canvas the canvas for drawing
  146.          */
  147.         private void drawValue(Canvas canvas) {
  148.                 valuePaint.setColor(VALUE_TEXT_COLOR);
  149.                 valuePaint.drawableState = getDrawableState();

  150.                 Rect bounds = new Rect();
  151.                 itemsLayout.getLineBounds(visibleItems / 2, bounds);

  152.                 // draw label
  153.                 if (labelLayout != null) {
  154.                         canvas.save();
  155.                         canvas.translate(itemsLayout.getWidth() + LABEL_OFFSET, bounds.top);
  156.                         labelLayout.draw(canvas);
  157.                         canvas.restore();
  158.                 }

  159.                 // draw current value
  160.                 if (valueLayout != null) {
  161.                         canvas.save();
  162.                         canvas.translate(0, bounds.top + scrollingOffset);
  163.                         valueLayout.draw(canvas);
  164.                         canvas.restore();
  165.                 }
  166.         }

  167.         /**
  168.          * Draws items
  169.          * @param canvas the canvas for drawing
  170.          */
  171.         private void drawItems(Canvas canvas) {
  172.                 canvas.save();
  173.                
  174.                 int top = itemsLayout.getLineTop(1);
  175.                 canvas.translate(0, - top + scrollingOffset);
  176.                
  177.                 itemsPaint.setColor(ITEMS_TEXT_COLOR);
  178.                 itemsPaint.drawableState = getDrawableState();
  179.                 itemsLayout.draw(canvas);
  180.                
  181.                 canvas.restore();
  182.         }

  183.         /**
  184.          * Draws rect for current value
  185.          * @param canvas the canvas for drawing
  186.          */
  187.         private void drawCenterRect(Canvas canvas) {
  188.                 int center = getHeight() / 2;
  189.                 int offset = getItemHeight() / 2;
  190.                 centerDrawable.setBounds(0, center - offset, getWidth(), center + offset);
  191.                 centerDrawable.draw(canvas);
  192.         }

  193.         @Override
  194.         public boolean onTouchEvent(MotionEvent event) {
  195.                 WheelAdapter adapter = getAdapter();
  196.                 if (adapter == null) {
  197.                         return true;
  198.                 }
  199.                
  200.                         if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {
  201.                         justify();
  202.                 }
  203.                 return true;
  204.         }
  205.        
  206.         /**
  207.          * Scrolls the wheel
  208.          * @param delta the scrolling value
  209.          */
  210.         private void doScroll(int delta) {
  211.                 scrollingOffset += delta;
  212.                
  213.                 int count = scrollingOffset / getItemHeight();
  214.                 int pos = currentItem - count;
  215.                 if (isCyclic && adapter.getItemsCount() > 0) {
  216.                         // fix position by rotating
  217.                         while (pos < 0) {
  218.                                 pos += adapter.getItemsCount();
  219.                         }
  220.                         pos %= adapter.getItemsCount();
  221.                 } else if (isScrollingPerformed) {
  222.                         //
  223.                         if (pos < 0) {
  224.                                 count = currentItem;
  225.                                 pos = 0;
  226.                         } else if (pos >= adapter.getItemsCount()) {
  227.                                 count = currentItem - adapter.getItemsCount() + 1;
  228.                                 pos = adapter.getItemsCount() - 1;
  229.                         }
  230.                 } else {
  231.                         // fix position
  232.                         pos = Math.max(pos, 0);
  233.                         pos = Math.min(pos, adapter.getItemsCount() - 1);
  234.                 }
  235.                
  236.                 int offset = scrollingOffset;
  237.                 if (pos != currentItem) {
  238.                         setCurrentItem(pos, false);
  239.                 } else {
  240.                         invalidate();
  241.                 }
  242.                
  243.                 // update offset
  244.                 scrollingOffset = offset - count * getItemHeight();
  245.                 if (scrollingOffset > getHeight()) {
  246.                         scrollingOffset = scrollingOffset % getHeight() + getHeight();
  247.                 }
  248.         }
  249.        
  250.         // gesture listener
  251.         private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {
  252.                 public boolean onDown(MotionEvent e) {
  253.                         if (isScrollingPerformed) {
  254.                                 scroller.forceFinished(true);
  255.                                 clearMessages();
  256.                                 return true;
  257.                         }
  258.                         return false;
  259.                 }
  260.                
  261.                 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
  262.                         startScrolling();
  263.                         doScroll((int)-distanceY);
  264.                         return true;
  265.                 }
  266.                
  267.                 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
  268.                         lastScrollY = currentItem * getItemHeight() + scrollingOffset;
  269.                         int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();
  270.                         int minY = isCyclic ? -maxY : 0;
  271.                         scroller.fling(0, lastScrollY, 0, (int) -velocityY / 2, 0, 0, minY, maxY);
  272.                         setNextMessage(MESSAGE_SCROLL);
  273.                         return true;
  274.                 }
  275.         };

  276.         // Messages
  277.         private final int MESSAGE_SCROLL = 0;
  278.         private final int MESSAGE_JUSTIFY = 1;
  279.        
  280.         /**
  281.          * Set next message to queue. Clears queue before.
  282.          *
  283.          * @param message the message to set
  284.          */
  285.         private void setNextMessage(int message) {
  286.                 clearMessages();
  287.                 animationHandler.sendEmptyMessage(message);
  288.         }

  289.         /**
  290.          * Clears messages from queue
  291.          */
  292.         private void clearMessages() {
  293.                 animationHandler.removeMessages(MESSAGE_SCROLL);
  294.                 animationHandler.removeMessages(MESSAGE_JUSTIFY);
  295.         }
  296.        
  297.         // animation handler
  298.         private Handler animationHandler = new Handler() {
  299.                 public void handleMessage(Message msg) {
  300.                         scroller.computeScrollOffset();
  301.                         int currY = scroller.getCurrY();
  302.                         int delta = lastScrollY - currY;
  303.                         lastScrollY = currY;
  304.                         if (delta != 0) {
  305.                                 doScroll(delta);
  306.                         }
  307.                        
  308.                         // scrolling is not finished when it comes to final Y
  309.                         // so, finish it manually
  310.                         if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) {
  311.                                 currY = scroller.getFinalY();
  312.                                 scroller.forceFinished(true);
  313.                         }
  314.                         if (!scroller.isFinished()) {
  315.                                 animationHandler.sendEmptyMessage(msg.what);
  316.                         } else if (msg.what == MESSAGE_SCROLL) {
  317.                                 justify();
  318.                         } else {
  319.                                 finishScrolling();
  320.                         }
  321.                 }
  322.         };
  323.        
  324.         /**
  325.          * Justifies wheel
  326.          */
  327.         private void justify() {
  328.                 if (adapter == null) {
  329.                         return;
  330.                 }
  331.                
  332.                 lastScrollY = 0;
  333.                 int offset = scrollingOffset;
  334.                 int itemHeight = getItemHeight();
  335.                 boolean needToIncrease = offset > 0 ? currentItem < adapter.getItemsCount() : currentItem > 0;
  336.                 if ((isCyclic || needToIncrease) && Math.abs((float) offset) > (float) itemHeight / 2) {
  337.                         if (offset < 0)
  338.                                 offset += itemHeight + MIN_DELTA_FOR_SCROLLING;
  339.                         else
  340.                                 offset -= itemHeight + MIN_DELTA_FOR_SCROLLING;
  341.                 }
  342.                 if (Math.abs(offset) > MIN_DELTA_FOR_SCROLLING) {
  343.                         scroller.startScroll(0, 0, 0, offset, SCROLLING_DURATION);
  344.                         setNextMessage(MESSAGE_JUSTIFY);
  345.                 } else {
  346.                         finishScrolling();
  347.                 }
  348.         }
  349.        
  350.         /**
  351.          * Starts scrolling
  352.          */
  353.         private void startScrolling() {
  354.                 if (!isScrollingPerformed) {
  355.                         isScrollingPerformed = true;
  356.                         notifyScrollingListenersAboutStart();
  357.                 }
  358.         }

  359.         /**
  360.          * Finishes scrolling
  361.          */
  362.         void finishScrolling() {
  363.                 if (isScrollingPerformed) {
  364.                         notifyScrollingListenersAboutEnd();
  365.                         isScrollingPerformed = false;
  366.                 }
  367.                 invalidateLayouts();
  368.                 invalidate();
  369.         }
  370.                
  371.         /**
  372.          * Scroll the wheel
  373.          * @param itemsToSkip items to scroll
  374.          * @param time scrolling duration
  375.          */
  376.         public void scroll(int itemsToScroll, int time) {
  377.                 scroller.forceFinished(true);
  378.                 lastScrollY = scrollingOffset;
  379.                 int offset = itemsToScroll * getItemHeight();               
  380.                 scroller.startScroll(0, lastScrollY, 0, offset - lastScrollY, time);
  381.                 setNextMessage(MESSAGE_SCROLL);               
  382.                 startScrolling();
  383.         }
复制代码

在629行到744行的代码是绘制图形,747行onTouchEvent()里面主要是调用了882行的justify()方法,用于调整画面,

  1. @Override
  2.         public boolean onTouchEvent(MotionEvent event) {
  3.                 WheelAdapter adapter = getAdapter();
  4.                 if (adapter == null) {
  5.                         return true;
  6.                 }
  7.                
  8.                         if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {
  9.                         justify();
  10.                 }
  11.                 return true;
  12.         }
复制代码
  1. /**
  2.          * Justifies wheel
  3.          */
  4.         private void justify() {
  5.                 if (adapter == null) {
  6.                         return;
  7.                 }
  8.                
  9.                 lastScrollY = 0;
  10.                 int offset = scrollingOffset;
  11.                 int itemHeight = getItemHeight();
  12.                 boolean needToIncrease = offset > 0 ? currentItem < adapter.getItemsCount() : currentItem > 0;
  13.                 if ((isCyclic || needToIncrease) && Math.abs((float) offset) > (float) itemHeight / 2) {
  14.                         if (offset < 0)
  15.                                 offset += itemHeight + MIN_DELTA_FOR_SCROLLING;
  16.                         else
  17.                                 offset -= itemHeight + MIN_DELTA_FOR_SCROLLING;
  18.                 }
  19.                 if (Math.abs(offset) > MIN_DELTA_FOR_SCROLLING) {
  20.                         scroller.startScroll(0, 0, 0, offset, SCROLLING_DURATION);
  21.                         setNextMessage(MESSAGE_JUSTIFY);
  22.                 } else {
  23.                         finishScrolling();
  24.                 }
  25.         }
复制代码

我们看下重写的系统回调函数onMeasure()(用于测量各个控件距离,父子控件空间大小等):

  1. @Override
  2.         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3.                 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  4.                 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  5.                 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  6.                 int heightSize = MeasureSpec.getSize(heightMeasureSpec);

  7.                 int width = calculateLayoutWidth(widthSize, widthMode);

  8.                 int height;
  9.                 if (heightMode == MeasureSpec.EXACTLY) {
  10.                         height = heightSize;
  11.                 } else {
  12.                         height = getDesiredHeight(itemsLayout);

  13.                         if (heightMode == MeasureSpec.AT_MOST) {
  14.                                 height = Math.min(height, heightSize);
  15.                         }
  16.                 }

  17.                 setMeasuredDimension(width, height);
  18.         }
复制代码

里面用到了532行calculateLayoutWidth()的方法,就是计算Layout的宽度,在calculateLayoutWidth()这个方法里面调用了

  1. /**
  2.          * Creates layouts
  3.          * @param widthItems width of items layout
  4.          * @param widthLabel width of label layout
  5.          */
  6.         private void createLayouts(int widthItems, int widthLabel) {
  7.                 if (itemsLayout == null || itemsLayout.getWidth() > widthItems) {
  8.                         itemsLayout = new StaticLayout(buildText(isScrollingPerformed), itemsPaint, widthItems,
  9.                                         widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,
  10.                                         1, ADDITIONAL_ITEM_HEIGHT, false);
  11.                 } else {
  12.                         itemsLayout.increaseWidthTo(widthItems);
  13.                 }

  14.                 if (!isScrollingPerformed && (valueLayout == null || valueLayout.getWidth() > widthItems)) {
  15.                         String text = getAdapter() != null ? getAdapter().getItem(currentItem) : null;
  16.                         valueLayout = new StaticLayout(text != null ? text : "",
  17.                                         valuePaint, widthItems, widthLabel > 0 ?
  18.                                                         Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER,
  19.                                                         1, ADDITIONAL_ITEM_HEIGHT, false);
  20.                 } else if (isScrollingPerformed) {
  21.                         valueLayout = null;
  22.                 } else {
  23.                         valueLayout.increaseWidthTo(widthItems);
  24.                 }

  25.                 if (widthLabel > 0) {
  26.                         if (labelLayout == null || labelLayout.getWidth() > widthLabel) {
  27.                                 labelLayout = new StaticLayout(label, valuePaint,
  28.                                                 widthLabel, Layout.Alignment.ALIGN_NORMAL, 1,
  29.                                                 ADDITIONAL_ITEM_HEIGHT, false);
  30.                         } else {
  31.                                 labelLayout.increaseWidthTo(widthLabel);
  32.                         }
  33.                 }
  34.         }
复制代码

然后我们接着看onDraw()方法:

  1. @Override
  2.         protected void onDraw(Canvas canvas) {
  3.                 super.onDraw(canvas);
  4.                
  5.                 if (itemsLayout == null) {
  6.                         if (itemsWidth == 0) {
  7.                                 calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);
  8.                         } else {
  9.                                 createLayouts(itemsWidth, labelWidth);
  10.                         }
  11.                 }

  12.                 if (itemsWidth > 0) {
  13.                         canvas.save();
  14.                         // Skip padding space and hide a part of top and bottom items
  15.                         canvas.translate(PADDING, -ITEM_OFFSET);
  16.                         drawItems(canvas);
  17.                         drawValue(canvas);
  18.                         canvas.restore();
  19.                 }

  20.                 drawCenterRect(canvas);
  21.                 drawShadows(canvas);
  22.         }
复制代码

在onDraw方法中,也调用了CreateLayout()方法,然后在后面调用drawCenterRect()、drawItems()、drawValue()、绘制阴影drawShadows()两个方法:

  1. /**
  2.          * Draws shadows on top and bottom of control
  3.          * @param canvas the canvas for drawing
  4.          */
  5.         private void drawShadows(Canvas canvas) {
  6.                 topShadow.setBounds(0, 0, getWidth(), getHeight() / visibleItems);
  7.                 topShadow.draw(canvas);


  8.                 bottomShadow.setBounds(0, getHeight() - getHeight() / visibleItems,
  9.                                 getWidth(), getHeight());
  10.                 bottomShadow.draw(canvas);
  11.         }


  12.         /**
  13.          * Draws value and label layout
  14.          * @param canvas the canvas for drawing
  15.          */
  16.         private void drawValue(Canvas canvas) {
  17.                 valuePaint.setColor(VALUE_TEXT_COLOR);
  18.                 valuePaint.drawableState = getDrawableState();


  19.                 Rect bounds = new Rect();
  20.                 itemsLayout.getLineBounds(visibleItems / 2, bounds);


  21.                 // draw label
  22.                 if (labelLayout != null) {
  23.                         canvas.save();
  24.                         canvas.translate(itemsLayout.getWidth() + LABEL_OFFSET, bounds.top);
  25.                         labelLayout.draw(canvas);
  26.                         canvas.restore();
  27.                 }


  28.                 // draw current value
  29.                 if (valueLayout != null) {
  30.                         canvas.save();
  31.                         canvas.translate(0, bounds.top + scrollingOffset);
  32.                         valueLayout.draw(canvas);
  33.                         canvas.restore();
  34.                 }
  35.         }


  36.         /**
  37.          * Draws items
  38.          * @param canvas the canvas for drawing
  39.          */
  40.         private void drawItems(Canvas canvas) {
  41.                 canvas.save();
  42.                
  43.                 int top = itemsLayout.getLineTop(1);
  44.                 canvas.translate(0, - top + scrollingOffset);
  45.                
  46.                 itemsPaint.setColor(ITEMS_TEXT_COLOR);
  47.                 itemsPaint.drawableState = getDrawableState();
  48.                 itemsLayout.draw(canvas);
  49.                
  50.                 canvas.restore();
  51.         }


  52.         /**
  53.          * Draws rect for current value
  54.          * @param canvas the canvas for drawing
  55.          */
  56.         private void drawCenterRect(Canvas canvas) {
  57.                 int center = getHeight() / 2;
  58.                 int offset = getItemHeight() / 2;
  59.                 centerDrawable.setBounds(0, center - offset, getWidth(), center + offset);
  60.                 centerDrawable.draw(canvas);
  61.         }
复制代码

主要就是通过canvas类进行图形的绘制。
       最后,我们看下840行定义的手势监听:

  1. // gesture listener
  2.         private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {
  3.                 public boolean onDown(MotionEvent e) {
  4.                         if (isScrollingPerformed) {
  5.                                 scroller.forceFinished(true);
  6.                                 clearMessages();
  7.                                 return true;
  8.                         }
  9.                         return false;
  10.                 }
  11.                
  12.                 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
  13.                         startScrolling();
  14.                         doScroll((int)-distanceY);
  15.                         return true;
  16.                 }
  17.                
  18.                 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
  19.                         lastScrollY = currentItem * getItemHeight() + scrollingOffset;
  20.                         int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() * getItemHeight();
  21.                         int minY = isCyclic ? -maxY : 0;
  22.                         scroller.fling(0, lastScrollY, 0, (int) -velocityY / 2, 0, 0, minY, maxY);
  23.                         setNextMessage(MESSAGE_SCROLL);
  24.                         return true;
  25.                 }
  26.         };
复制代码

里面主要调用的方法:clearMessages()、startScrolling()、doScroll()、setNextMessage(),先看下中间的两个方法开始滑动和滑动

  1. /**
  2.          * Scrolls the wheel
  3.          * @param delta the scrolling value
  4.          */
  5.         private void doScroll(int delta) {
  6.                 scrollingOffset += delta;               
  7.                 int count = scrollingOffset / getItemHeight();
  8.                 int pos = currentItem - count;
  9.                 if (isCyclic && adapter.getItemsCount() > 0) {
  10.                         // fix position by rotating
  11.                         while (pos < 0) {
  12.                                 pos += adapter.getItemsCount();
  13.                         }
  14.                         pos %= adapter.getItemsCount();
  15.                 } else if (isScrollingPerformed) {
  16.                         //
  17.                         if (pos < 0) {
  18.                                 count = currentItem;
  19.                                 pos = 0;
  20.                         } else if (pos >= adapter.getItemsCount()) {
  21.                                 count = currentItem - adapter.getItemsCount() + 1;
  22.                                 pos = adapter.getItemsCount() - 1;
  23.                         }
  24.                 } else {
  25.                         // fix position
  26.                         pos = Math.max(pos, 0);
  27.                         pos = Math.min(pos, adapter.getItemsCount() - 1);
  28.                 }
  29.                
  30.                 int offset = scrollingOffset;
  31.                 if (pos != currentItem) {
  32.                         setCurrentItem(pos, false);
  33.                 } else {
  34.                         invalidate();
  35.                 }
  36.                
  37.                 // update offset
  38.                 scrollingOffset = offset - count * getItemHeight();
  39.                 if (scrollingOffset > getHeight()) {
  40.                         scrollingOffset = scrollingOffset % getHeight() + getHeight();
  41.                 }
  42.         }
复制代码
  1. /**
  2.          * Starts scrolling
  3.          */
  4.         private void startScrolling() {
  5.                 if (!isScrollingPerformed) {
  6.                         isScrollingPerformed = true;
  7.                         notifyScrollingListenersAboutStart();
  8.                 }
  9.         }
复制代码

在startScrolling方法里面有287行的notifyScrollingListenersAboutStart函数。再看clearMessages()、setMessageNext()

  1. private void setNextMessage(int message) {
  2.                 clearMessages();
  3.                 animationHandler.sendEmptyMessage(message);
  4.         }


  5.         /**
  6.          * Clears messages from queue
  7.          */
  8.         private void clearMessages() {
  9.                 animationHandler.removeMessages(MESSAGE_SCROLL);
  10.                 animationHandler.removeMessages(MESSAGE_JUSTIFY);
  11.         }
复制代码

里面使用到了animationHandler,用来传递动画有段的操作:

  1. // animation handler
  2.         private Handler animationHandler = new Handler() {
  3.                 public void handleMessage(Message msg) {
  4.                         scroller.computeScrollOffset();
  5.                         int currY = scroller.getCurrY();
  6.                         int delta = lastScrollY - currY;
  7.                         lastScrollY = currY;
  8.                         if (delta != 0) {
  9.                                 doScroll(delta);
  10.                         }
  11.                        
  12.                         // scrolling is not finished when it comes to final Y
  13.                         // so, finish it manually
  14.                         if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) {
  15.                                 currY = scroller.getFinalY();
  16.                                 scroller.forceFinished(true);
  17.                         }
  18.                         if (!scroller.isFinished()) {
  19.                                 animationHandler.sendEmptyMessage(msg.what);
  20.                         } else if (msg.what == MESSAGE_SCROLL) {
  21.                                 justify();
  22.                         } else {
  23.                                 finishScrolling();
  24.                         }
  25.                 }
  26.         };
复制代码

里面调用了finishScrolling()

  1. /**
  2.          * Finishes scrolling
  3.          */
  4.         void finishScrolling() {
  5.                 if (isScrollingPerformed) {
  6.                         notifyScrollingListenersAboutEnd();
  7.                         isScrollingPerformed = false;
  8.                 }
  9.                 invalidateLayouts();
  10.                 invalidate();
  11.         }
复制代码





 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
仿iPhone热门应用栏是一个非常流行的产品趋势,许多手机品牌已经开始推出类似的产品。仿iPhone热门应用栏是指手机系统底部的一排常用应用图标,用户可以轻松访问和切换这些应用程序。 优点: 1. 提高用户体验:仿iPhone热门应用栏将常用的应用程序放置在屏幕底部,用户可以轻松地操作和访问这些应用。这种设计提高了用户的便利性和操作效率。 2. 提升工作效率:对于经常使用特定应用程序的用户来说,将它们放在热门应用栏中可以节省寻找和打开应用的时间,提高工作效率。 3. 可定制性:用户可以根据自己的需求和喜好,在热门应用栏中添加或删除特定的应用程序。这种可定制性使得用户可以根据自己的使用习惯进行个性化设置。 缺点: 1. 屏幕空间受限:仿iPhone热门应用栏通常会占据屏幕底部的一部分空间,可能会减少用户在其他应用程序中可见的屏幕空间。这对于一些需要较大屏幕空间才能展示内容的应用程序来说可能是一个问题。 2. 可能存在兼容性问题:由于不同手机品牌和操作系统之间存在差异,仿iPhone热门应用栏可能在某些设备上无法正常运行或显示。这需要开发人员在设计和开发过程中考虑到兼容性问题。 总结: 仿iPhone热门应用栏是一种流行的设计趋势,它提供了一种方便和高效的方式来访问和切换常用应用程序。虽然它具有许多优点,但也存在一些限制和挑战,特别是在屏幕空间和设备兼容性方面。因此,在设计和实施仿iPhone热门应用栏时,需要综合考虑这些因素,并根据特定的用户需求进行定制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值