java 仿苹果控件_自定义控件省市区:仿苹果级联菜单

/** 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.*/

packagecn.newcom.wheelview;/*** 自定义控件*/

importjava.util.LinkedList;importjava.util.List;importcn.newcom.pickdemo.R;importcn.newcom.pickdemo.R.drawable;importandroid.content.Context;importandroid.graphics.Canvas;importandroid.graphics.Paint;importandroid.graphics.Rect;importandroid.graphics.drawable.Drawable;importandroid.graphics.drawable.GradientDrawable;importandroid.graphics.drawable.GradientDrawable.Orientation;importandroid.os.Handler;importandroid.os.Message;importandroid.text.Layout;importandroid.text.StaticLayout;importandroid.text.TextPaint;importandroid.util.AttributeSet;importandroid.util.FloatMath;importandroid.view.GestureDetector;importandroid.view.GestureDetector.SimpleOnGestureListener;importandroid.view.MotionEvent;importandroid.view.View;importandroid.view.animation.Interpolator;importandroid.widget.Scroller;/*** Numeric wheel view.*/

public class WheelView extendsView {/**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

privateTextPaint itemsPaint;privateTextPaint valuePaint;//Layouts

privateStaticLayout itemsLayout;privateStaticLayout labelLayout;privateStaticLayout valueLayout;//Label & background

privateString label;privateDrawable centerDrawable;//Shadows drawables

privateGradientDrawable topShadow;privateGradientDrawable bottomShadow;//Scrolling

private booleanisScrollingPerformed;private intscrollingOffset;//Scrolling animation

privateGestureDetector gestureDetector;privateScroller scroller;private intlastScrollY;//Cyclic

boolean isCyclic = false;//Listeners

private List changingListeners = new LinkedList();private List scrollingListeners = new LinkedList();/*** Constructor*/

public WheelView(Context context, AttributeSet attrs, intdefStyle) {super(context, attrs, defStyle);

initData(context);

}/*** Constructor*/

publicWheelView(Context context, AttributeSet attrs) {super(context, attrs);

initData(context);

}/*** Constructor*/

publicWheelView(Context context) {super(context);

initData(context);

}/*** Initializes class data

*@paramcontext the context*/

private voidinitData(Context context) {

gestureDetector= newGestureDetector(context, gestureListener);

gestureDetector.setIsLongpressEnabled(false);

scroller= newScroller(context);

}/*** Gets wheel adapter

*@returnthe adapter*/

publicWheelAdapter getAdapter() {returnadapter;

}/*** Sets wheel adapter

*@paramadapter the new wheel adapter*/

public voidsetAdapter(WheelAdapter adapter) {this.adapter =adapter;

invalidateLayouts();

invalidate();

}/*** Set the the specified scrolling interpolator

*@paraminterpolator the interpolator*/

public voidsetInterpolator(Interpolator interpolator) {

scroller.forceFinished(true);

scroller= newScroller(getContext(), interpolator);

}/*** Gets count of visible items

*

*@returnthe count of visible items*/

public intgetVisibleItems() {returnvisibleItems;

}/*** Sets count of visible items

*

*@paramcount

* the new count*/

public void setVisibleItems(intcount) {

visibleItems=count;

invalidate();

}/*** Gets label

*

*@returnthe label*/

publicString getLabel() {returnlabel;

}/*** Sets label

*

*@paramnewLabel

* the label to set*/

public voidsetLabel(String newLabel) {if (label == null || !label.equals(newLabel)) {

label=newLabel;

labelLayout= null;

invalidate();

}

}/*** Adds wheel changing listener

*@paramlistener the listener*/

public voidaddChangingListener(OnWheelChangedListener listener) {

changingListeners.add(listener);

}/*** Removes wheel changing listener

*@paramlistener the listener*/

public voidremoveChangingListener(OnWheelChangedListener listener) {

changingListeners.remove(listener);

}/*** Notifies changing listeners

*@paramoldValue the old wheel value

*@paramnewValue the new wheel value*/

protected void notifyChangingListeners(int oldValue, intnewValue) {for(OnWheelChangedListener listener : changingListeners) {

listener.onChanged(this, oldValue, newValue);

}

}/*** Adds wheel scrolling listener

*@paramlistener the listener*/

public voidaddScrollingListener(OnWheelScrollListener listener) {

scrollingListeners.add(listener);

}/*** Removes wheel scrolling listener

*@paramlistener the listener*/

public voidremoveScrollingListener(OnWheelScrollListener listener) {

scrollingListeners.remove(listener);

}/*** Notifies listeners about starting scrolling*/

protected voidnotifyScrollingListenersAboutStart() {for(OnWheelScrollListener listener : scrollingListeners) {

listener.onScrollingStarted(this);

}

}/*** Notifies listeners about ending scrolling*/

protected voidnotifyScrollingListenersAboutEnd() {for(OnWheelScrollListener listener : scrollingListeners) {

listener.onScrollingFinished(this);

}

}/*** Gets current value

*

*@returnthe current value*/

public intgetCurrentItem() {returncurrentItem;

}/*** Sets the current item. Does nothing when index is wrong.

*

*@paramindex the item index

*@paramanimated the animation flag*/

public void setCurrentItem(int index, booleananimated) {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.

*

*@paramindex the item index*/

public void setCurrentItem(intindex) {

setCurrentItem(index,false);

}/*** Tests if wheel is cyclic. That means before the 1st item there is shown the last one

*@returntrue if wheel is cyclic*/

public booleanisCyclic() {returnisCyclic;

}/*** Set wheel cyclic flag

*@paramisCyclic the flag to set*/

public void setCyclic(booleanisCyclic) {this.isCyclic =isCyclic;

invalidate();

invalidateLayouts();

}/*** Invalidates layouts*/

private voidinvalidateLayouts() {

itemsLayout= null;

valueLayout= null;

scrollingOffset= 0;

}/*** Initializes resources*/

private voidinitResourcesIfNecessary() {if (itemsPaint == null) {

itemsPaint= newTextPaint(Paint.ANTI_ALIAS_FLAG|Paint.FAKE_BOLD_TEXT_FLAG);//itemsPaint.density = getResources().getDisplayMetrics().density;

itemsPaint.setTextSize(TEXT_SIZE);

}if (valuePaint == null) {

valuePaint= newTextPaint(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= newGradientDrawable(Orientation.TOP_BOTTOM, SHADOWS_COLORS);

}if (bottomShadow == null) {

bottomShadow= newGradientDrawable(Orientation.BOTTOM_TOP, SHADOWS_COLORS);

}

setBackgroundResource(R.drawable.wheel_bg);

}/*** Calculates desired height for layout

*

*@paramlayout

* the source layout

*@returnthe desired layout height*/

private intgetDesiredHeight(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());returndesired;

}/*** Returns text item by index

*@paramindex the item index

*@returnthe item or null*/

private String getTextItem(intindex) {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;returnadapter.getItem(index);

}/*** Builds text depending on current value

*

*@paramuseCurrentValue

*@returnthe text*/

private String buildText(booleanuseCurrentValue) {

StringBuilder itemsText= newStringBuilder();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");

}

}returnitemsText.toString();

}/*** Returns the max item length that can be present

*@returnthe max length*/

private intgetMaxTextLength() {

WheelAdapter adapter=getAdapter();if (adapter == null) {return 0;

}int adapterLength =adapter.getMaximumLength();if (adapterLength > 0) {returnadapterLength;

}

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()

maxText=text;

}

}return maxText != null ? maxText.length() : 0;

}/*** Returns height of wheel item

*@returnthe item height*/

private intgetItemHeight() {if (itemHeight != 0) {returnitemHeight;

}else if (itemsLayout != null && itemsLayout.getLineCount() > 2) {

itemHeight= itemsLayout.getLineTop(2) - itemsLayout.getLineTop(1);returnitemHeight;

}return getHeight() /visibleItems;

}/*** Calculates control width and creates text layouts

*@paramwidthSize the input layout width

*@parammode the layout mode

*@returnthe calculated control width*/

private int calculateLayoutWidth(int widthSize, intmode) {

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=widthSize;

recalculate= true;

}

}if(recalculate) {//recalculate width

int pureWidth = width - LABEL_OFFSET - 2 *PADDING;if (pureWidth <= 0) {

itemsWidth= labelWidth = 0;

}if (labelWidth > 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);

}returnwidth;

}/*** Creates layouts

*@paramwidthItems width of items layout

*@paramwidthLabel width of label layout*/

private void createLayouts(int widthItems, intwidthLabel) {if (itemsLayout == null || itemsLayout.getWidth() >widthItems) {

itemsLayout= newStaticLayout(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= newStaticLayout(label, valuePaint,

widthLabel, Layout.Alignment.ALIGN_NORMAL,1,

ADDITIONAL_ITEM_HEIGHT,false);

}else{

labelLayout.increaseWidthTo(widthLabel);

}

}

}

@Overrideprotected void onMeasure(int widthMeasureSpec, intheightMeasureSpec) {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);intheight;if (heightMode ==MeasureSpec.EXACTLY) {

height=heightSize;

}else{

height=getDesiredHeight(itemsLayout);if (heightMode ==MeasureSpec.AT_MOST) {

height=Math.min(height, heightSize);

}

}

setMeasuredDimension(width, height);

}

@Overrideprotected voidonDraw(Canvas canvas) {super.onDraw(canvas);if (itemsLayout == null) {if (itemsWidth == 0) {

calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);

}else{

createLayouts(itemsWidth, labelWidth);

}

}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

*@paramcanvas the canvas for drawing*/

private voiddrawShadows(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

*@paramcanvas the canvas for drawing*/

private voiddrawValue(Canvas canvas) {

valuePaint.setColor(VALUE_TEXT_COLOR);

valuePaint.drawableState=getDrawableState();

Rect bounds= newRect();

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

*@paramcanvas the canvas for drawing*/

private voiddrawItems(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

*@paramcanvas the canvas for drawing*/

private voiddrawCenterRect(Canvas canvas) {int center = getHeight() / 2;int offset = getItemHeight() / 2;

centerDrawable.setBounds(0, center - offset, getWidth(), center +offset);

centerDrawable.draw(canvas);

}

@Overridepublic booleanonTouchEvent(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

*@paramdelta the scrolling value*/

private void doScroll(intdelta) {

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) {

setCurrentItem(pos,false);

}else{

invalidate();

}//update offset

scrollingOffset = offset - count *getItemHeight();if (scrollingOffset >getHeight()) {

scrollingOffset= scrollingOffset % getHeight() +getHeight();

}

}//gesture listener

private SimpleOnGestureListener gestureListener = newSimpleOnGestureListener() {public booleanonDown(MotionEvent e) {if(isScrollingPerformed) {

scroller.forceFinished(true);

clearMessages();return true;

}return false;

}public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, floatdistanceY) {

startScrolling();

doScroll((int)-distanceY);return true;

}public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, floatvelocityY) {

lastScrollY= currentItem * getItemHeight() +scrollingOffset;int maxY = isCyclic ? 0x7FFFFFFF : adapter.getItemsCount() *getItemHeight();int minY = isCyclic ? -maxY : 0;

scroller.fling(0, lastScrollY, 0, (int) -velocityY / 2, 0, 0, minY, maxY);

setNextMessage(MESSAGE_SCROLL);return true;

}

};//Messages

private final int MESSAGE_SCROLL = 0;private final int MESSAGE_JUSTIFY = 1;/*** Set next message to queue. Clears queue before.

*

*@parammessage the message to set*/

private void setNextMessage(intmessage) {

clearMessages();

animationHandler.sendEmptyMessage(message);

}/*** Clears messages from queue*/

private voidclearMessages() {

animationHandler.removeMessages(MESSAGE_SCROLL);

animationHandler.removeMessages(MESSAGE_JUSTIFY);

}//animation handler

private Handler animationHandler = newHandler() {public voidhandleMessage(Message msg) {

scroller.computeScrollOffset();int currY =scroller.getCurrY();int delta = lastScrollY -currY;

lastScrollY=currY;if (delta != 0) {

doScroll(delta);

}//scrolling is not finished when it comes to final Y//so, finish it manually

if (Math.abs(currY - scroller.getFinalY())

currY=scroller.getFinalY();

scroller.forceFinished(true);

}if (!scroller.isFinished()) {

animationHandler.sendEmptyMessage(msg.what);

}else if (msg.what ==MESSAGE_SCROLL) {

justify();

}else{

finishScrolling();

}

}

};/*** Justifies wheel*/

private voidjustify() {if (adapter == null) {return;

}

lastScrollY= 0;int offset =scrollingOffset;int itemHeight =getItemHeight();boolean needToIncrease = offset > 0 ? currentItem < adapter.getItemsCount() : currentItem > 0;if ((isCyclic || needToIncrease) && Math.abs((float) offset) > (float) itemHeight / 2) {if (offset < 0)

offset+= itemHeight +MIN_DELTA_FOR_SCROLLING;elseoffset-= itemHeight +MIN_DELTA_FOR_SCROLLING;

}if (Math.abs(offset) >MIN_DELTA_FOR_SCROLLING) {

scroller.startScroll(0, 0, 0, offset, SCROLLING_DURATION);

setNextMessage(MESSAGE_JUSTIFY);

}else{

finishScrolling();

}

}/*** Starts scrolling*/

private voidstartScrolling() {if (!isScrollingPerformed) {

isScrollingPerformed= true;

notifyScrollingListenersAboutStart();

}

}/*** Finishes scrolling*/

voidfinishScrolling() {if(isScrollingPerformed) {

notifyScrollingListenersAboutEnd();

isScrollingPerformed= false;

}

invalidateLayouts();

invalidate();

}/*** Scroll the wheel

*@paramitemsToSkip items to scroll

*@paramtime scrolling duration*/

public void scroll(int itemsToScroll, inttime) {

scroller.forceFinished(true);

lastScrollY=scrollingOffset;int offset = itemsToScroll *getItemHeight();

scroller.startScroll(0, lastScrollY, 0, offset -lastScrollY, time);

setNextMessage(MESSAGE_SCROLL);

startScrolling();

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值