首先,先看下效果圖: html
這三張圖分別是使用滾動控件實現城市,隨機數和時間三個簡單的例子,固然,界面有點簡陋,下面咱們就以時間這個爲例,開始解析一下。 java
首先,先看下佈局文件: android
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_marginTop="12dp" android:orientation="vertical" android:background="@drawable/layout_bg"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_gravity="center_horizontal" android:paddingLeft="12dp" android:paddingRight="12dp" android:paddingTop="10dp"> <kankan.wheel.widget.WheelView android:id="@+id/hour" android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_weight="1"/> <kankan.wheel.widget.WheelView android:id="@+id/mins" android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_weight="1"/> </LinearLayout> <TimePicker android:id="@+id/time" android:layout_marginTop="12dp" android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_weight="1"/> </LinearLayout>
public class TimeActivity extends Activity { // Time changed flag private boolean timeChanged = false; // private boolean timeScrolled = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.time_layout); final WheelView hours = (WheelView) findViewById(R.id.hour); hours.setAdapter(new NumericWheelAdapter(0, 23)); hours.setLabel("hours"); final WheelView mins = (WheelView) findViewById(R.id.mins); mins.setAdapter(new NumericWheelAdapter(0, 59, "%02d")); mins.setLabel("mins"); mins.setCyclic(true); final TimePicker picker = (TimePicker) findViewById(R.id.time); picker.setIs24HourView(true); // set current time Calendar c = Calendar.getInstance(); int curHours = c.get(Calendar.HOUR_OF_DAY); int curMinutes = c.get(Calendar.MINUTE); hours.setCurrentItem(curHours); mins.setCurrentItem(curMinutes); picker.setCurrentHour(curHours); picker.setCurrentMinute(curMinutes); // add listeners addChangingListener(mins, "min"); addChangingListener(hours, "hour"); OnWheelChangedListener wheelListener = new OnWheelChangedListener() { public void onChanged(WheelView wheel, int oldValue, int newValue) { if (!timeScrolled) { timeChanged = true; picker.setCurrentHour(hours.getCurrentItem()); picker.setCurrentMinute(mins.getCurrentItem()); timeChanged = false; } } }; hours.addChangingListener(wheelListener); mins.addChangingListener(wheelListener); OnWheelScrollListener scrollListener = new OnWheelScrollListener() { public void onScrollingStarted(WheelView wheel) { timeScrolled = true; } public void onScrollingFinished(WheelView wheel) { timeScrolled = false; timeChanged = true; picker.setCurrentHour(hours.getCurrentItem()); picker.setCurrentMinute(mins.getCurrentItem()); timeChanged = false; } }; hours.addScrollingListener(scrollListener); mins.addScrollingListener(scrollListener); picker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() { public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { if (!timeChanged) { hours.setCurrentItem(hourOfDay, true); mins.setCurrentItem(minute, true); } } }); } /** * Adds changing listener for wheel that updates the wheel label * @param wheel the wheel * @param label the wheel label */ private void addChangingListener(final WheelView wheel, final String label) { wheel.addChangingListener(new OnWheelChangedListener() { public void onChanged(WheelView wheel, int oldValue, int newValue) { wheel.setLabel(newValue != 1 ? label + "s" : label); } }); } }
/** * Wheel scrolled listener interface. */ public interface OnWheelScrollListener { /** * Callback method to be invoked when scrolling started. * @param wheel the wheel view whose state has changed. */ void onScrollingStarted(WheelView wheel); /** * Callback method to be invoked when scrolling ended. * @param wheel the wheel view whose state has changed. */ void onScrollingFinished(WheelView wheel); }
public interface OnWheelChangedListener { /** * Callback method to be invoked when current item changed * @param wheel the wheel view whose state has changed * @param oldValue the old value of current item * @param newValue the new value of current item */ void onChanged(WheelView wheel, int oldValue, int newValue); }
而後如今,咱們進入WheelView類,看一下他是如何構建,首先,WheelView繼承了View類。代碼的22行到45行是導入的所須要的類。從54行到135行是聲明一些變量和類: less
/** Scrolling duration */ private static final int SCROLLING_DURATION = 400; /** Minimum delta for scrolling */ private static final int MIN_DELTA_FOR_SCROLLING = 1; /** Current value & label text color */ private static final int VALUE_TEXT_COLOR = 0xF0000000; /** Items text color */ private static final int ITEMS_TEXT_COLOR = 0xFF000000; /** Top and bottom shadows colors */ private static final int[] SHADOWS_COLORS = new int[] { 0xFF111111, 0x00AAAAAA, 0x00AAAAAA }; /** Additional items height (is added to standard text item height) */ private static final int ADDITIONAL_ITEM_HEIGHT = 15; /** Text size */ private static final int TEXT_SIZE = 24; /** Top and bottom items offset (to hide that) */ private static final int ITEM_OFFSET = TEXT_SIZE / 5; /** Additional width for items layout */ private static final int ADDITIONAL_ITEMS_SPACE = 10; /** Label offset */ private static final int LABEL_OFFSET = 8; /** Left and right padding value */ private static final int PADDING = 10; /** Default count of visible items */ private static final int DEF_VISIBLE_ITEMS = 5; // Wheel Values private WheelAdapter adapter = null; private int currentItem = 0; // Widths private int itemsWidth = 0; private int labelWidth = 0; // Count of visible items private int visibleItems = DEF_VISIBLE_ITEMS; // Item height private int itemHeight = 0; // Text paints private TextPaint itemsPaint; private TextPaint valuePaint; // Layouts private StaticLayout itemsLayout; private StaticLayout labelLayout; private StaticLayout valueLayout; // Label & background private String label; private Drawable centerDrawable; // Shadows drawables private GradientDrawable topShadow; private GradientDrawable bottomShadow; // Scrolling private boolean isScrollingPerformed; private int scrollingOffset; // Scrolling animation private GestureDetector gestureDetector; private Scroller scroller; private int lastScrollY; // Cyclic boolean isCyclic = false; // Listeners private List<OnWheelChangedListener> changingListeners = new LinkedList<OnWheelChangedListener>(); private List<OnWheelScrollListener> scrollingListeners = new LinkedList<OnWheelScrollListener>();
StaticLayout is a Layout for text that will not be edited after it is laid out. Use DynamicLayout for text that may change. 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.
還使用到了Drawable、Text'Paint、GradientDrawable、GestureDetector、Scroller類,在開發文檔中,GradientDrawable的概述: ide
A Drawable with a color gradient for buttons, backgrounds, etc. It can be defined in an XML file with the <shape> element. For more information, see the guide to Drawable Resources.
TextPaint的概述: 函數
TextPaint is an extension of Paint that leaves room for some extra data used during text measuring and drawing.
GestureDetector:手勢檢測,看下開發文檔中關於該類的概述:
佈局
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).
140行到156行是構造方法,175到183行是set和getAdapter。在193行,setInterpolator()方法,設置interPolator這個動畫接口,咱們看下這個接口的概述: post
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.
203行到213行設置顯示的item條數。在setVisibleItems()方法裏面調用了View的invalidate()方法,看下文檔中對該方法的介紹: 動畫
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().
223行到233行是設置Label,既後面圖片中的hours. ui
245行到296行是設置監聽,在上面已經簡單的說了一下,這裏不在累述。
307行到349行是設置正被選中item,就是在那個陰影條框下的那個部分,比較簡單。裏面主要調用了scroll這個方法:
/** * Scroll the wheel * @param itemsToSkip items to scroll * @param time scrolling duration */ public void scroll(int itemsToScroll, int time) { scroller.forceFinished(true); lastScrollY = scrollingOffset; int offset = itemsToScroll * getItemHeight(); scroller.startScroll(0, lastScrollY, 0, offset - lastScrollY, time); setNextMessage(MESSAGE_SCROLL); startScrolling(); }
357行到365行是設置item數據可否循環使用。
384行的initResourcesIfNecessary()方法,從字面意思,若是須要的初始化資源。
private void initResourcesIfNecessary() { if (itemsPaint == null) { itemsPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.FAKE_BOLD_TEXT_FLAG); //itemsPaint.density = getResources().getDisplayMetrics().density; itemsPaint.setTextSize(TEXT_SIZE); } if (valuePaint == null) { valuePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.FAKE_BOLD_TEXT_FLAG | Paint.DITHER_FLAG); //valuePaint.density = getResources().getDisplayMetrics().density; valuePaint.setTextSize(TEXT_SIZE); valuePaint.setShadowLayer(0.1f, 0, 0.1f, 0xFFC0C0C0); } if (centerDrawable == null) { centerDrawable = getContext().getResources().getDrawable(R.drawable.wheel_val); } if (topShadow == null) { topShadow = new GradientDrawable(Orientation.TOP_BOTTOM, SHADOWS_COLORS); } if (bottomShadow == null) { bottomShadow = new GradientDrawable(Orientation.BOTTOM_TOP, SHADOWS_COLORS); } setBackgroundResource(R.drawable.wheel_bg); }
這個方法就是初始化在532行calculateLayoutWidth()方法中調用了這個方法,同時調用了487行的getMaxTextLength()這個方法。
471行getTextItem(int index)經過一個索引獲取該item的文本。
這是第一部分,沒有多少有太多意思的地方,重點的地方在之後532行到940行的內容,另起一篇,開始分析,這一篇先到這。
最後是下載地址: