自制日期选择器(datepicker)
扫描二维码
随时随地手机看文章
在网上看过很多关于datepicker的列子,效果就是我想要的,但界面却和我的项目不同,因此在网上的例子上做做手脚,改变下外观,先说下改动的地方,再将代码放上去。
1、主要改动关键是在WheelView类:protected void onDraw(Canvas canvas)方法中,drawCenterRect(canvas)的位置放置
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (itemsLayout == null) { if (itemsWidth == 0) { calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY); } else { createLayouts(itemsWidth, labelWidth); } } drawCenterRect(canvas); // 放在此处,字在背景色上 if (itemsWidth > 0) { canvas.save(); // Skip padding space and hide a part of top and bottom items canvas.translate(PADDING, -ITEM_OFFSET); drawItems(canvas); drawValue(canvas); canvas.restore(); } // drawCenterRect(canvas);// 放在此处,字在背景色下 drawShadows(canvas); }
2、在wheel_val.xml文件中设置选中项背景色
下面公布下代码:
NumericWheelAdapter类:
/* * Copyright 2010 Yuri Kanivets * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lakala.pay.easycashregisterphone2.view.datepicker; /** * Numeric Wheel adapter. */ public class NumericWheelAdapter implements WheelAdapter { /** The default min value */ public static final int DEFAULT_MAX_VALUE = 9; /** The default max value */ private static final int DEFAULT_MIN_VALUE = 0; // Values private int minValue; private int maxValue; // format private String format; /** * Default constructor */ public NumericWheelAdapter() { this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE); } /** * Constructor * @param minValue the wheel min value * @param maxValue the wheel max value */ public NumericWheelAdapter(int minValue, int maxValue) { this(minValue, maxValue, null); } /** * Constructor * @param minValue the wheel min value * @param maxValue the wheel max value * @param format the format string */ public NumericWheelAdapter(int minValue, int maxValue, String format) { this.minValue = minValue; this.maxValue = maxValue; this.format = format; } public String getItem(int index) { if (index >= 0 && index < getItemsCount()) { int value = minValue + index; return format != null ? String.format(format, value) : Integer.toString(value); } return null; } public int getItemsCount() { return maxValue - minValue + 1; } public int getMaximumLength() { int max = Math.max(Math.abs(maxValue), Math.abs(minValue)); int maxLen = Integer.toString(max).length(); if (minValue < 0) { maxLen++; } return maxLen; } }
OnWheelChangedListener类:
/* * Copyright 2010 Yuri Kanivets * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lakala.pay.easycashregisterphone2.view.datepicker; /** * Wheel changed listener interface. *The currentItemChanged() method is called whenever current wheel positions is changed: *New Wheel position is set *Wheel view is scrolled */ 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); }
OnWheelScrollListener类:
/* * Copyright 2010 Yuri Kanivets * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lakala.pay.easycashregisterphone2.view.datepicker; /** * 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); }
WheelAdapter类:
/* * Copyright 2010 Yuri Kanivets * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lakala.pay.easycashregisterphone2.view.datepicker; public interface WheelAdapter { /** * Gets items count * @return the count of wheel items */ public int getItemsCount(); /** * Gets a wheel item by index. * * @param index the item index * @return the wheel item text or null */ public String getItem(int index); /** * Gets maximum item length. It is used to determine the wheel width. * If -1 is returned there will be used the default wheel width. * * @return the maximum item length or -1 */ public int getMaximumLength(); }
WheelView类:
/* * Android Wheel Control. * https://code.google.com/p/android-wheel/ * * Copyright 2010 Yuri Kanivets * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package kankan.wheel.widget; import java.util.LinkedList; import java.util.List; import org.unism.wang.R; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.GradientDrawable.Orientation; import android.os.Handler; import android.os.Message; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.util.AttributeSet; import android.util.FloatMath; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; import android.view.View; import android.view.animation.Interpolator; import android.widget.Scroller; /** * Numeric wheel view. * * @author Yuri Kanivets */ public class WheelView extends View { /** * 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 = 0xFFFFFFFF; /** * Items text color * * * 选项 的 文字 的 颜色 */ private static final int ITEMS_TEXT_COLOR = 0xFF000000; /** * Top and bottom shadows colors * * * 顶部和底部阴影 的 颜色 * 选择器 顶部和底部颜色是渐变的,这里指定一个渐变色的数组 */ private static final int[] SHADOWS_COLORS = new int[] { 0xFFFFFFFF, 0x00EEEED5, 0x00EEEED5 }; /** * Additional items height (is added to standard text item height) * * * 附加项的高度项的高度 (单位应该是dp) * 从后面getDesiredHeight() 函数看出,这个值应该是平分给每一个选项的。 * 类似于设置行距吧,不过这是一个总和,也就是有5个可见项,那么每个可见项的附加高就是 ADDITIONAL_ITEM_HEIGHT/5 */ 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) * * * 这个是选项在顶部和底部被抵消的值。 * 怎么解释呢~ 其实试一下就知道了, * 比如说在picker中显示的五项(中间那个是选中的),剩余4是没选中的。 * 在没选中的这4项中,位于顶部和底部的项,会用阴影遮挡(遮挡一部分,这样用户就明白是需要滑动操作了) * 这里设定的值,就是指定遮挡的size,这里的默认值 TEXT_SIZE / 5 是遮挡了1/5的字号 (那么单位也应该是sp吧) */ private static final int ITEM_OFFSET = TEXT_SIZE / 5; /** * Additional width for items layout * * * 附加项空间? 不理解~~还是试试吧 * 应该是项的宽,这个属性应该是受制于外边区域的, 设置的太宽里面的文字显示会出问题 * 具体影响 有待进一步实验证明 */ private static final int ADDITIONAL_ITEMS_SPACE = 10; /** * Label offset * * * 标签抵消 (作用未知) 用1,8,和800 三个值实验(8是默认值) 效果是一样的。 */ private static final int LABEL_OFFSET = 8; /** * Left and right padding value * * * 填充 * 这个选项的内部填充,如果选项是个TextView的话,那这里设置的即是TextView的填充 * =。=!后面代码还米有看,item是啥我也不知道 */ private static final int PADDING = 10; /** * Default count of visible items * * * 默认可见项的数目。就是picker中显示几项 */ private static final int DEF_VISIBLE_ITEMS = 5; // Wheel Values /** * Wheel Values * * * 适配器,items就通过适配器来提供的吧 */ private WheelAdapter adapter = null; /** * Wheel Values * * * 当前项 */ private int currentItem = 0; // Widths /** * Widths * * * 做了实验 把值设为100 没有任何变化 */ private int itemsWidth = 0; /** * Widths * * * 也做了实验 把值设为100 没有任何变化 */ private int labelWidth = 0; // Count of visible items /** * Count of visible items * * * 可见项目的条数 */ private int visibleItems = DEF_VISIBLE_ITEMS; // Item height /** * Item height * * * 是item的高 */ private int itemHeight = 0; // Text paints /** * Text paints * * * 选中文本的颜色 */ private TextPaint itemsPaint; /** * Text paints * * * 未选中文本的颜色 */ private TextPaint valuePaint; // Layouts /** * Layouts * * * 选项 的 布局 */ private StaticLayout itemsLayout; /** * Layouts * * * 标签 的 布局 */ private StaticLayout labelLayout; /** * Layouts * * * 选中项 的 布局 */ private StaticLayout valueLayout; // Label & background /** * Label & background * * * 标签 */ private String label; /** * Label & background * 中间的几何体 * 选中区域的背景 */ private Drawable centerDrawable; // Shadows drawables /** * Shadows drawables * * * 上边 和 底部 的阴影部分的背景资源 */ private GradientDrawable topShadow; private GradientDrawable bottomShadow; // Scrolling /** * Scrolling * * * 执行滚动? */ private boolean isScrollingPerformed; /** * Scrolling * * * 滚动抵消? */ private int scrollingOffset; // Scrolling animation /** * Scrolling animation * * * 手势检测器 */ private GestureDetector gestureDetector; /** * Scrolling animation * * * 卷轴 */ private Scroller scroller; /** * Scrolling animation * * * 最后的 卷轴Y */ private int lastScrollY; // Cyclic /** * Cyclic * * * 是否循环 */ boolean isCyclic = false; // Listeners /** * Listeners * * * 控件改变监听器的 集合 */ private ListchangingListeners = new LinkedList(); /** * Listeners * * * 控件滚动监听器的 集合 */ private ListscrollingListeners = new LinkedList(); /** * Constructor * * * 构造器 并实例了手势检测器 和 卷轴 */ public WheelView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initData(context); } /** * Constructor * * * 构造器 并实例了手势检测器 和 卷轴 */ public WheelView(Context context, AttributeSet attrs) { super(context, attrs); initData(context); } /** * Constructor * * * 构造器 并实例了手势检测器 和 卷轴 */ public WheelView(Context context) { super(context); initData(context); } /** * Initializes class data * @param context the context * * * 数据初始化 * 就是把手势检测器 和 卷轴类 实例了 * 这个方法在所有的构造器中都被调用了 */ private void initData(Context context) { gestureDetector = new GestureDetector(context, gestureListener); gestureDetector.setIsLongpressEnabled(false); //这个没看出来有什么用,不设置,或设置成true都不影响效果 scroller = new Scroller(context); } /** * Gets wheel adapter * @return the adapter * * * 获得适配器 */ public WheelAdapter getAdapter() { return adapter; } /** * Sets wheel adapter * @param adapter the new wheel adapter * * * 设置适配器 * 还会界面进行了重绘 */ public void setAdapter(WheelAdapter adapter) { this.adapter = adapter; invalidateLayouts(); invalidate(); } /** * Set the the specified scrolling interpolator * @param interpolator the interpolator * * * 作用也是设置 卷轴的吧,是通过动画插补器来实例 卷轴对象 */ public void setInterpolator(Interpolator interpolator) { scroller.forceFinished(true); scroller = new Scroller(getContext(), interpolator); } /** * Gets count of visible items * * @return the count of visible items * * * 获得可见项的条数 */ public int getVisibleItems() { return visibleItems; } /** * Sets count of visible items * * @param count * the new count * * * 设置可见项的条数 并重绘view */ public void setVisibleItems(int count) { visibleItems = count; invalidate(); } /** * Gets label * * @return the label * * * 获得标签 */ public String getLabel() { return label; } /** * Sets label * * @param newLabel * the label to set * * * 设置标签 */ public void setLabel(String newLabel) { if (label == null || !label.equals(newLabel)) { label = newLabel; labelLayout = null; invalidate(); } } /** * Adds wheel changing listener * @param listener the listener * * * 添加控件改变监听器 */ public void addChangingListener(OnWheelChangedListener listener) { changingListeners.add(listener); } /** * Removes wheel changing listener * @param listener the listener * * * 移除控件改变监听器 */ public void removeChangingListener(OnWheelChangedListener listener) { changingListeners.remove(listener); } /** * Notifies changing listeners * @param oldValue the old wheel value * @param newValue the new wheel value * * * 通知 改变监听器, * 循环 控件改变监听器集合, 并依次调用它们的onChenge方法 */ protected void notifyChangingListeners(int oldValue, int newValue) { for (OnWheelChangedListener listener : changingListeners) { listener.onChanged(this, oldValue, newValue); } } /** * Adds wheel scrolling listener * @param listener the listener * * * 添加控件滚动监听器 */ public void addScrollingListener(OnWheelScrollListener listener) { scrollingListeners.add(listener); } /** * Removes wheel scrolling listener * @param listener the listener * * * 移除控件滚动监听器 */ public void removeScrollingListener(OnWheelScrollListener listener) { scrollingListeners.remove(listener); } /** * Notifies listeners about starting scrolling * * * 通知控件滚动监听器调用开始滚动的方法 */ protected void notifyScrollingListenersAboutStart() { for (OnWheelScrollListener listener : scrollingListeners) { listener.onScrollingStarted(this); } } /** * Notifies listeners about ending scrolling * * * 通知控件滚动监听器调用结束滚动的方法 */ protected void notifyScrollingListenersAboutEnd() { for (OnWheelScrollListener listener : scrollingListeners) { listener.onScrollingFinished(this); } } /** * Gets current value * * @return the current value * * * 返回当前项的索引 */ public int getCurrentItem() { return currentItem; } /** * Sets the current item. Does nothing when index is wrong. * * @param index the item index * @param animated the animation flag * * * 设置当前项 如果输入错误的索引,则控件什么都不会做哟 * 第二个参数是设置是否使用滚动动画的 */ public void setCurrentItem(int index, boolean animated) { if (adapter == null || adapter.getItemsCount() == 0) { return; // throw? } if (index < 0 || index >= adapter.getItemsCount()) { if (isCyclic) { while (index < 0) { index += adapter.getItemsCount(); } index %= adapter.getItemsCount(); } else{ return; // throw? } } if (index != currentItem) { if (animated) { scroll(index - currentItem, SCROLLING_DURATION); } else { invalidateLayouts(); int old = currentItem; currentItem = index; notifyChangingListeners(old, currentItem); invalidate(); } } } /** * Sets the current item w/o animation. Does nothing when index is wrong. * * @param index the item index * * * 设置当前选中项,默认是不启动动画的 */ public void setCurrentItem(int index) { setCurrentItem(index, false); } /** * Tests if wheel is cyclic. That means before the 1st item there is shown the last one * @return true if wheel is cyclic * * * 是否循环显示 */ public boolean isCyclic() { return isCyclic; } /** * Set wheel cyclic flag * @param isCyclic the flag to set * * * 设置是否循环显示 */ public void setCyclic(boolean isCyclic) { this.isCyclic = isCyclic; invalidate(); invalidateLayouts(); } /** * Invalidates layouts * * * 重绘布局 * 方法将 选项布局itemsLayout 和 选中项布局 valueLayout 赋值为null * 同事将 滚动抵消?scrollingOffset 设置为0 * (这个scrollingOffset 还没搞明白做什么用) */ private void invalidateLayouts() { itemsLayout = null; valueLayout = null; scrollingOffset = 0; } /** * Initializes resources * * * 初始化源 * 这个方法是这样的, 判断前面定义的画笔、背景资源等私有属性的值,如果是null就重新从静态常量中取值,并付给响应的属性。 * 如果这些必要属性为空的话,这个函数应该起到了初始化的作用 */ 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); } /** * Calculates desired height for layout * * @param layout * the source layout * @return the desired layout height * * * 获得理想的控件高度,并保证其不低于建议的最小高度 */ private int getDesiredHeight(Layout layout) { if (layout == null) { return 0; } int desired = getItemHeight() * visibleItems - ITEM_OFFSET * 2 - ADDITIONAL_ITEM_HEIGHT; // Check against our minimum height desired = Math.max(desired, getSuggestedMinimumHeight()); return desired; } /** * Returns text item by index * @param index the item index * @return the item or null * * * 指定索引,获得选项的文本值(Sring) * 如果索引超出范围,控件又不是循环的(isCyclic),则返回null * 如果是循环的情况,方法内部进行了处理, * 为负数则+count, 然会取余 */ private String getTextItem(int index) { if (adapter == null || adapter.getItemsCount() == 0) { return null; } int count = adapter.getItemsCount(); if ((index < 0 || index >= count) && !isCyclic) { return null; } else { while (index < 0) { index = count + index; } } index %= count; return adapter.getItem(index); } /** * Builds text depending on current value * * @param useCurrentValue * @return the text * * * 依赖当前项 构建文本 * 获得一个字符串,如果当前项目的索引为5, 可见项目数为3 * 则字符串的值为 getTextItem(3).append("n").getTextItem(4).append("n").getTextItem(5).append("n").getTextItem(6).append("n").getTextItem(7) * 如果 参数useCurrentValue为false * 则返回的字符串为 getTextItem(3).append("n").getTextItem(4).append("n").getTextItem(6).append("n").getTextItem(7) * 如果getTextItem(i)返回null, 则不会向返回值中追加 */ private String buildText(boolean useCurrentValue) { StringBuilder itemsText = new StringBuilder(); int addItems = visibleItems / 2 + 1; for (int i = currentItem - addItems; i <= currentItem + addItems; i++) { if (useCurrentValue || i != currentItem) { String text = getTextItem(i); if (text != null) { itemsText.append(text); } } if (i < currentItem + addItems) { itemsText.append("n"); } } return itemsText.toString(); } /** * Returns the max item length that can be present * @return the max length * * * 获得最大的文本长度 * 后面计算控件宽度用的 */ private int getMaxTextLength() { WheelAdapter adapter = getAdapter(); if (adapter == null) { return 0; } int adapterLength = adapter.getMaximumLength(); if (adapterLength > 0) { return adapterLength; } String maxText = null; int addItems = visibleItems / 2; for (int i = Math.max(currentItem - addItems, 0); i < Math.min(currentItem + visibleItems, adapter.getItemsCount()); i++) { String text = adapter.getItem(i); if (text != null && (maxText == null || maxText.length() < text.length())) { maxText = text; } }//这个循环的范围没看明白呀,起始值不考虑循环吗? 上限为什么是 当前项索引+可见项目数? return maxText != null ? maxText.length() : 0; } /** * Returns height of wheel item * @return the item height * * * 获得选项高 */ private int getItemHeight() { if (itemHeight != 0) { return itemHeight; } else if (itemsLayout != null && itemsLayout.getLineCount() > 2) { itemHeight = itemsLayout.getLineTop(2) - itemsLayout.getLineTop(1); return itemHeight; }//如果是itemlayout 为什么要用 第三行的top减第一行的top呢,不是应该返回layout的高嘛?没看明白 return getHeight() / visibleItems; } /** * Calculates control width and creates text layouts * @param widthSize the input layout width * @param mode the layout mode * @return the calculated control width * * * 计算布局宽 */ private int calculateLayoutWidth(int widthSize, int mode) { initResourcesIfNecessary(); int width = widthSize; int maxLength = getMaxTextLength(); if (maxLength > 0) { float textWidth = FloatMath.ceil(Layout.getDesiredWidth("0", itemsPaint)); itemsWidth = (int) (maxLength * textWidth); } else { itemsWidth = 0; } itemsWidth += ADDITIONAL_ITEMS_SPACE; // make it some more labelWidth = 0; if (label != null && label.length() > 0) { labelWidth = (int) FloatMath.ceil(Layout.getDesiredWidth(label, valuePaint)); } boolean recalculate = false; if (mode == MeasureSpec.EXACTLY) { width = widthSize; recalculate = true; } else { width = itemsWidth + labelWidth + 2 * PADDING; if (labelWidth > 0) { width += LABEL_OFFSET; } // Check against our minimum width width = Math.max(width, getSuggestedMinimumWidth()); if (mode == MeasureSpec.AT_MOST && widthSize < width) { width = widthSize; recalculate = true; } } if (recalculate) { // recalculate width int pureWidth = width - LABEL_OFFSET - 2 * PADDING; if (pureWidth 0) { double newWidthItems = (double) itemsWidth * pureWidth / (itemsWidth + labelWidth); itemsWidth = (int) newWidthItems; labelWidth = pureWidth - itemsWidth; } else { itemsWidth = pureWidth + LABEL_OFFSET; // no label } } if (itemsWidth > 0) { createLayouts(itemsWidth, labelWidth); } return width; } /** * Creates layouts * @param widthItems width of items layout * @param widthLabel width of label layout * * * 创建布局 */ private void createLayouts(int widthItems, int widthLabel) { if (itemsLayout == null || itemsLayout.getWidth() > widthItems) { itemsLayout = new StaticLayout(buildText(isScrollingPerformed), itemsPaint, widthItems, widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER, 1, ADDITIONAL_ITEM_HEIGHT, false); } else { itemsLayout.increaseWidthTo(widthItems); } if (!isScrollingPerformed && (valueLayout == null || valueLayout.getWidth() > widthItems)) { String text = getAdapter() != null ? getAdapter().getItem(currentItem) : null; valueLayout = new StaticLayout(text != null ? text : "", valuePaint, widthItems, widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_CENTER, 1, ADDITIONAL_ITEM_HEIGHT, false); } else if (isScrollingPerformed) { valueLayout = null; } else { valueLayout.increaseWidthTo(widthItems); } if (widthLabel > 0) { if (labelLayout == null || labelLayout.getWidth() > widthLabel) { labelLayout = new StaticLayout(label, valuePaint, widthLabel, Layout.Alignment.ALIGN_NORMAL, 1, ADDITIONAL_ITEM_HEIGHT, false); } else { labelLayout.increaseWidthTo(widthLabel); } } } /** * 重写onMeasure 设置尺寸 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width = calculateLayoutWidth(widthSize, widthMode); int height; if (heightMode == MeasureSpec.EXACTLY) { height = heightSize; } else { height = getDesiredHeight(itemsLayout); if (heightMode == MeasureSpec.AT_MOST) { height = Math.min(height, heightSize); } } setMeasuredDimension(width, height); } /** * 绘制 */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (itemsLayout == null) { if (itemsWidth == 0) { calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY); } else { createLayouts(itemsWidth, labelWidth); } } drawCenterRect(canvas); // 放在此处,字在背景色上 if (itemsWidth > 0) { canvas.save(); // Skip padding space and hide a part of top and bottom items canvas.translate(PADDING, -ITEM_OFFSET); drawItems(canvas); drawValue(canvas); canvas.restore(); } // drawCenterRect(canvas);// 放在此处,字在背景色下 drawShadows(canvas); } /** * Draws shadows on top and bottom of control * @param canvas the canvas for drawing * * * 绘制控件顶端 和底部的 阴影区域 * 需要传入画布对象 */ private void drawShadows(Canvas canvas) { topShadow.setBounds(0, 0, getWidth(), getHeight() / visibleItems); topShadow.draw(canvas); bottomShadow.setBounds(0, getHeight() - getHeight() / visibleItems, getWidth(), getHeight()); bottomShadow.draw(canvas); } /** * Draws value and label layout * @param canvas the canvas for drawing * * * 绘制选中项 和 标签 */ private void drawValue(Canvas canvas) { valuePaint.setColor(VALUE_TEXT_COLOR); valuePaint.drawableState = getDrawableState(); Rect bounds = new Rect(); itemsLayout.getLineBounds(visibleItems / 2, bounds); // draw label if (labelLayout != null) { canvas.save(); canvas.translate(itemsLayout.getWidth() + LABEL_OFFSET, bounds.top); labelLayout.draw(canvas); canvas.restore(); } // draw current value if (valueLayout != null) { canvas.save(); canvas.translate(0, bounds.top + scrollingOffset); valueLayout.draw(canvas); canvas.restore(); } } /** * Draws items * @param canvas the canvas for drawing * * * 绘制选项 */ private void drawItems(Canvas canvas) { canvas.save(); int top = itemsLayout.getLineTop(1); canvas.translate(0, - top + scrollingOffset); itemsPaint.setColor(ITEMS_TEXT_COLOR); itemsPaint.drawableState = getDrawableState(); itemsLayout.draw(canvas); canvas.restore(); } /** * Draws rect for current value * @param canvas the canvas for drawing * * * 绘制中间的矩形区域 */ private void drawCenterRect(Canvas canvas) { int center = getHeight() / 2; int offset = getItemHeight() / 2; centerDrawable.setBounds(0, center - offset, getWidth(), center + offset); centerDrawable.draw(canvas); } /** * 触摸事件的 回调方法 * 看到了吧 如果适配器 adapter是null 的话,这里是什么都不会做的。 * 如果适配器不为空,将MotionEvent传递给手势识别器。 并判断是否已ACTION_UP * 如果是说明操作已结束 调用justify()方法。 * return true. 不会泄露 */ @Override public boolean onTouchEvent(MotionEvent event) { WheelAdapter adapter = getAdapter(); if (adapter == null) { return true; } if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) { justify(); } return true; } /** * Scrolls the wheel * @param delta the scrolling value * * * 滚动 * 好像只是重新定义了 scrollingOffset的值, * 执行滚动的操作是这里吗? * 先往后看吧。 */ private void doScroll(int delta) { scrollingOffset += delta; int count = scrollingOffset / getItemHeight(); int pos = currentItem - count; if (isCyclic && adapter.getItemsCount() > 0) { // fix position by rotating while (pos < 0) { pos += adapter.getItemsCount(); } pos %= adapter.getItemsCount(); } else if (isScrollingPerformed) { // if (pos < 0) { count = currentItem; pos = 0; } else if (pos >= adapter.getItemsCount()) { count = currentItem - adapter.getItemsCount() + 1; pos = adapter.getItemsCount() - 1; } } else { // fix position pos = Math.max(pos, 0); pos = Math.min(pos, adapter.getItemsCount() - 1); } int offset = scrollingOffset; if (pos != currentItem)