展会信息港展会大全

android仿iPhone滚轮控件实现及源码分析(二)
来源:互联网   发布日期:2015-10-03 10:51:28   浏览:3932次  

导读:在上一篇android仿iPhone滚轮控件实现及源码分析(一)简单的说了下架构还有效果图,但是关于图形的绘制各方面的代码在532行到940行,如果写在一篇文章里面,可能会导致文章太长,效果不好,所以自作聪明的分成了......

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

首先,先把代码贴出来:

/**

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

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

}

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

}

}

}

@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);

}

}

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

}

@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

*/

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

setCurrentItem(pos, false);

} else {

invalidate();

}

// update offset

scrollingOffset = offset - count * getItemHeight();

if (scrollingOffset > getHeight()) {

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

}

}

// gesture listener

private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {

public boolean onDown(MotionEvent e) {

if (isScrollingPerformed) {

scroller.forceFinished(true);

clearMessages();

return true;

}

return false;

}

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

startScrolling();

doScroll((int)-distanceY);

return true;

}

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

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.

*

* @param message the message to set

*/

private void setNextMessage(int message) {

clearMessages();

animationHandler.sendEmptyMessage(message);

}

/**

* Clears messages from queue

*/

private void clearMessages() {

animationHandler.removeMessages(MESSAGE_SCROLL);

animationHandler.removeMessages(MESSAGE_JUSTIFY);

}

// animation handler

private Handler animationHandler = new Handler() {

public void handleMessage(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()) < MIN_DELTA_FOR_SCROLLING) {

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 void justify() {

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;

else

offset -= 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 void startScrolling() {

if (!isScrollingPerformed) {

isScrollingPerformed = true;

notifyScrollingListenersAboutStart();

}

}

/**

* Finishes scrolling

*/

void finishScrolling() {

if (isScrollingPerformed) {

notifyScrollingListenersAboutEnd();

isScrollingPerformed = false;

}

invalidateLayouts();

invalidate();

}

/**

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

}

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

@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;

}

/**

* Justifies wheel

*/

private void justify() {

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;

else

offset -= 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();

}

}

我们看下重写的系统回调函数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);

}

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

/**

* 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);

}

}

}

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

@Override

protected void onDraw(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);

}

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

/**

* 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);

}

主要就是通过canvas类进行图形的绘制。

最后,我们看下840行定义的手势监听:

// gesture listener

private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {

public boolean onDown(MotionEvent e) {

if (isScrollingPerformed) {

scroller.forceFinished(true);

clearMessages();

return true;

}

return false;

}

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

startScrolling();

doScroll((int)-distanceY);

return true;

}

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

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;

}

};

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

/**

* Scrolls the wheel

* @param delta the scrolling value

*/

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

setCurrentItem(pos, false);

} else {

invalidate();

}

// update offset

scrollingOffset = offset - count * getItemHeight();

if (scrollingOffset > getHeight()) {

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

}

}

/**

* Starts scrolling

*/

private void startScrolling() {

if (!isScrollingPerformed) {

isScrollingPerformed = true;

notifyScrollingListenersAboutStart();

}

}

在startScrolling方法里面有287行的notifyScrollingListenersAboutStart函数。

再看clearMessages()、setMessageNext()

private void setNextMessage(int message) {

clearMessages();

animationHandler.sendEmptyMessage(message);

}

/**

* Clears messages from queue

*/

private void clearMessages() {

animationHandler.removeMessages(MESSAGE_SCROLL);

animationHandler.removeMessages(MESSAGE_JUSTIFY);

}

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

// animation handler

private Handler animationHandler = new Handler() {

public void handleMessage(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()) < MIN_DELTA_FOR_SCROLLING) {

currY = scroller.getFinalY();

scroller.forceFinished(true);

}

if (!scroller.isFinished()) {

animationHandler.sendEmptyMessage(msg.what);

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

justify();

} else {

finishScrolling();

}

}

};

里面调用了finishScrolling()

/**

* Finishes scrolling

*/

void finishScrolling() {

if (isScrollingPerformed) {

notifyScrollingListenersAboutEnd();

isScrollingPerformed = false;

}

invalidateLayouts();

invalidate();

}

赞助本站

人工智能实验室

相关热词: iPhone 滚轮

AiLab云推荐
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港