/* Copyright 2012 Aphid Mobile 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.jwzhangjie.videoplayer.flip; import android.view.MotionEvent; import android.view.View; import com.jwzhangjie.videoplayer.fliputils.AphidLog; import com.jwzhangjie.videoplayer.fliputils.TextureUtils; import com.jwzhangjie.videoplayer.fliputils.UI; import junit.framework.Assert; import javax.microedition.khronos.opengles.GL10; public class FlipCards { private static final float ACCELERATION = 0.65f; private static final float MOVEMENT_RATE = 1.5f; private static final int MAX_TIP_ANGLE = 60; private static final int MAX_TOUCH_MOVE_ANGLE = 15; private static final float MIN_MOVEMENT = 4f; private static final int STATE_INIT = 0; private static final int STATE_TOUCH = 1; private static final int STATE_AUTO_ROTATE = 2; private ViewDualCards frontCards; private ViewDualCards backCards; private float accumulatedAngle = 0f; private boolean forward = true; private int animatedFrame = 0; private int state = STATE_INIT; private boolean orientationVertical = true; private float lastPosition = -1; private FlipViewController controller; private volatile boolean visible = false; private volatile boolean firstDrawFinished = false; private int maxIndex = 0; private int lastPageIndex; public FlipCards(FlipViewController controller, boolean orientationVertical) { this.controller = controller; frontCards = new ViewDualCards(orientationVertical); backCards = new ViewDualCards(orientationVertical); this.orientationVertical = orientationVertical; } public boolean isVisible() { return visible; } public void setVisible(boolean visible) { this.visible = visible; } public boolean isFirstDrawFinished() { return firstDrawFinished; } public void setFirstDrawFinished(boolean firstDrawFinished) { this.firstDrawFinished = firstDrawFinished; } boolean refreshPageView(View view) { boolean match = false; if (frontCards.getView() == view) { frontCards.resetWithIndex(frontCards.getIndex()); match = true; } if (backCards.getView() == view) { backCards.resetWithIndex(backCards.getIndex()); match = true; } return match; } boolean refreshPage(int pageIndex) { boolean match = false; if (frontCards.getIndex() == pageIndex) { frontCards.resetWithIndex(pageIndex); match = true; } if (backCards.getIndex() == pageIndex) { backCards.resetWithIndex(pageIndex); match = true; } return match; } void refreshAllPages() { frontCards.resetWithIndex(frontCards.getIndex()); backCards.resetWithIndex(backCards.getIndex()); } public void reloadTexture(int frontIndex, View frontView, int backIndex, View backView) { synchronized (this) { boolean frontChanged = frontCards.loadView(frontIndex, frontView, controller.getAnimationBitmapFormat()); boolean backChanged = backCards.loadView(backIndex, backView, controller.getAnimationBitmapFormat()); if (AphidLog.ENABLE_DEBUG) { AphidLog .d("reloading texture: %s and %s; old views: %s, %s, front changed %s, back changed %s", frontView, backView, frontCards.getView(), backCards.getView(), frontChanged, backChanged); } if (AphidLog.ENABLE_DEBUG) { AphidLog.d("reloadTexture: activeIndex %d, front %d, back %d, angle %.1f", getPageIndexFromAngle(accumulatedAngle), frontIndex, backIndex, accumulatedAngle); } } } synchronized void resetSelection(int selection, int maxIndex) { UI.assertInMainThread(); //stop flip animation when selection is manually changed this.maxIndex = maxIndex; setVisible(false); setState(STATE_INIT); accumulatedAngle = selection * 180; frontCards.resetWithIndex(selection); backCards.resetWithIndex(selection + 1 < maxIndex ? selection + 1 : -1); controller.postHideFlipAnimation(); } public synchronized void draw(FlipRenderer renderer, GL10 gl) { frontCards.buildTexture(renderer, gl); backCards.buildTexture(renderer, gl); if (!TextureUtils.isValidTexture(frontCards.getTexture()) && !TextureUtils.isValidTexture(backCards.getTexture())) { return; } if (!visible) { return; } switch (state) { case STATE_INIT: case STATE_TOUCH: break; case STATE_AUTO_ROTATE: { animatedFrame++; float delta = (forward ? ACCELERATION : -ACCELERATION) * animatedFrame % 180; float oldAngle = accumulatedAngle; accumulatedAngle += delta; if (oldAngle < 0) { //bouncing back after flip backward and over the first page Assert.assertTrue(forward); if (accumulatedAngle >= 0) { accumulatedAngle = 0; setState(STATE_INIT); } } else { if (frontCards.getIndex() == maxIndex - 1 && oldAngle > frontCards.getIndex() * 180) { //bouncing back after flip forward and over the last page Assert.assertTrue(!forward); if (accumulatedAngle <= frontCards.getIndex() * 180) { setState(STATE_INIT); accumulatedAngle = frontCards.getIndex() * 180; } } else { if (forward) { Assert.assertTrue( "index of backCards should not be -1 when automatically flipping forward", backCards.getIndex() != -1); if (accumulatedAngle >= backCards.getIndex() * 180) { //moved to the next page accumulatedAngle = backCards.getIndex() * 180; setState(STATE_INIT); controller.postFlippedToView(backCards.getIndex()); swapCards(); backCards.resetWithIndex(frontCards.getIndex() + 1); } } else { //backward if (accumulatedAngle <= frontCards.getIndex() * 180) { //firstCards restored accumulatedAngle = frontCards.getIndex() * 180; setState(STATE_INIT); } } } } //ends of `if (oldAngle < 0) {} else {}` if (state == STATE_INIT) { controller.postHideFlipAnimation(); } else { controller.getSurfaceView().requestRender(); } } break; default: AphidLog.e("Invalid state: " + state); break; } float angle = getDisplayAngle(); if (angle < 0) { frontCards.getTopCard().setAxis(Card.AXIS_BOTTOM); frontCards.getTopCard().setAngle(-angle); frontCards.getTopCard().draw(gl); frontCards.getBottomCard().setAngle(0); frontCards.getBottomCard().draw(gl); //no need to draw backCards here } else { if (angle < 90) { //render front view over back view frontCards.getTopCard().setAngle(0); frontCards.getTopCard().draw(gl); backCards.getBottomCard().setAngle(0); backCards.getBottomCard().draw(gl); frontCards.getBottomCard().setAxis(Card.AXIS_TOP); frontCards.getBottomCard().setAngle(angle); frontCards.getBottomCard().draw(gl); } else { //render back view first frontCards.getTopCard().setAngle(0); frontCards.getTopCard().draw(gl); backCards.getTopCard().setAxis(Card.AXIS_BOTTOM); backCards.getTopCard().setAngle(180 - angle); backCards.getTopCard().draw(gl); backCards.getBottomCard().setAngle(0); backCards.getBottomCard().draw(gl); } } if ((frontCards.getView() == null || TextureUtils.isValidTexture(frontCards.getTexture())) && (backCards.getView() == null || TextureUtils.isValidTexture(backCards.getTexture())) ) firstDrawFinished = true; } public void invalidateTexture() { frontCards.abandonTexture(); backCards.abandonTexture(); } public synchronized boolean handleTouchEvent(MotionEvent event, boolean isOnTouchEvent) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // remember page we started on...记住开始的页面 lastPageIndex = getPageIndexFromAngle(accumulatedAngle); lastPosition = orientationVertical ? event.getY() : event.getX(); //如果是竖屏翻页,记住y坐标,如果是横屏翻页,记住x坐标 return isOnTouchEvent; case MotionEvent.ACTION_MOVE: float delta = //用来判断移动的方向,如果大于0,则正方向移动,如果小于0,则反方向移动 orientationVertical ? (lastPosition - event.getY()) : (lastPosition - event.getX()); if (Math.abs(delta) > controller.getTouchSlop()) { setState(STATE_TOUCH); forward = delta > 0; } if (state == STATE_TOUCH) { if (Math.abs(delta) > MIN_MOVEMENT) //ignore small movements 假如移动范围很小,则忽略不计 { forward = delta > 0; } controller.showFlipAnimation(); //开始滑动,同时opengl es进行渲染 float angleDelta; //将滑动的距离,换算成页面翻转的角度 if (orientationVertical) { angleDelta = 180 * delta / controller.getContentHeight() * MOVEMENT_RATE; } else { angleDelta = 180 * delta / controller.getContentWidth() * MOVEMENT_RATE; } if (Math.abs(angleDelta) > MAX_TOUCH_MOVE_ANGLE) //prevent large delta when moving too fast {//返回参数 a 的符号函数;如果参数是零,则返回零; 如果参数大于零,则返回 1.0;如果参数小于零,则返回-1.0 angleDelta = Math.signum(angleDelta) * MAX_TOUCH_MOVE_ANGLE; } // do not flip more than one page with one touch... 一次滑动,只能滑动一页 if (Math.abs(getPageIndexFromAngle(accumulatedAngle + angleDelta) - lastPageIndex) <= 1) { accumulatedAngle += angleDelta; } //Bounce the page for the first and the last page,防止页数越界 if (frontCards.getIndex() == maxIndex - 1) { //the last page if (accumulatedAngle > frontCards.getIndex() * 180) { accumulatedAngle = Math.min(accumulatedAngle, controller.isOverFlipEnabled() ? (frontCards.getIndex() * 180 + MAX_TIP_ANGLE) : (frontCards.getIndex() * 180)); } } if (accumulatedAngle < 0) { accumulatedAngle = Math.max(accumulatedAngle, controller.isOverFlipEnabled() ? -MAX_TIP_ANGLE : 0); } int anglePageIndex = getPageIndexFromAngle(accumulatedAngle); if (accumulatedAngle >= 0) { if (anglePageIndex != frontCards.getIndex()) { if (anglePageIndex == frontCards.getIndex() - 1) { //moved to previous page swapCards(); //frontCards becomes the backCards frontCards.resetWithIndex(backCards.getIndex() - 1); controller.flippedToView(anglePageIndex, false); } else if (anglePageIndex == frontCards.getIndex() + 1) { //moved to next page swapCards(); backCards.resetWithIndex(frontCards.getIndex() + 1); controller.flippedToView(anglePageIndex, false); } else { throw new RuntimeException(AphidLog.format( "Inconsistent states: anglePageIndex: %d, accumulatedAngle %.1f, frontCards %d, backCards %d", anglePageIndex, accumulatedAngle, frontCards.getIndex(), backCards.getIndex())); } } } lastPosition = orientationVertical ? event.getY() : event.getX(); controller.getSurfaceView().requestRender(); return true; } return isOnTouchEvent; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (state == STATE_TOUCH) { if (accumulatedAngle < 0) { forward = true; } else if (accumulatedAngle >= frontCards.getIndex() * 180 && frontCards.getIndex() == maxIndex - 1) { forward = false; } setState(STATE_AUTO_ROTATE); controller.getSurfaceView().requestRender(); } return isOnTouchEvent; } return false; } private void swapCards() { ViewDualCards tmp = frontCards; frontCards = backCards; backCards = tmp; } private void setState(int state) { if (this.state != state) { /* if (AphidLog.ENABLE_DEBUG) AphidLog.i("setState: from %d, to %d; angle %.1f", this.state, state, angle); */ this.state = state; animatedFrame = 0; } } private int getPageIndexFromAngle(float angle) { return ((int) angle) / 180; } private float getDisplayAngle() { return accumulatedAngle % 180; } }