完整项目结构:
登录注册UI设计:
登录成功跳转个人信息界面:
签到功能:
电影主页:(上下图片可以左右滑动查看热映电影)
查看所有热映电影:(可以上下滑动)
选择一部电影查看详细信息:
点击购票:
选位置:(左上角会出现一个定位预览界面,高度还原小程序UI界面)
点击购买并且完成付款:
付款成功自动跳转到消息页面,里面会显示购买好的电影票:
*
源代码:
Mainactivity.java
package com.example.myapplication;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Matrix;import android.graphics.Paint;import android.graphics.Path;import android.graphics.RectF;import android.os.Handler;import android.os.Message;import android.util.AttributeSet;import android.view.GestureDetector;import android.view.MotionEvent;import android.view.ScaleGestureDetector;import android.view.View;import android.widget.Toast;import java.util.ArrayList;public class MainActivity extends View { private static final String TAG = MainActivity.class.getName(); boolean isDebug; Paint paint = new Paint(); Matrix matrix = new Matrix(); int spacing = 6; int verSpacing = 10; int numberWidth = 20; int row = 0; int column = 0; Bitmap seatBitmap; Bitmap checkedSeatBitmap; Bitmap seatSoldBitmap; Bitmap overviewBitmap; Bitmap seat; int lastX; int lastY; int seatBitmapWidth; int seatBitmapHeight; boolean isNeedDrawSeatBitmap = true; float rectSize = 10; float rectWidth = 10; float overviewSpacing = 5; float overviewVerSpacing = 5; float overviewScale = 4.8f; float screenHeight; float screenWidthScale = 0.5f; int defaultScreenWidth; boolean isScaling; float scaleX, scaleY; boolean firstScale = true; public void setMaxSelected(int maxSelected) { this.maxSelected = maxSelected; } int maxSelected=Integer.MAX_VALUE; public void setSeatChecker(SeatChecker seatChecker) { this.seatChecker = seatChecker; invalidate(); } private SeatChecker seatChecker; public void setScreenName(String screenName) { this.screenName = screenName; } private String screenName = ""; ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.OnScaleGestureListener() { @Override public boolean onScale(ScaleGestureDetector detector) { isScaling = true; float scaleFactor = detector.getScaleFactor(); if (getMatrixScaleY() * scaleFactor > 3) { scaleFactor = 3 / getMatrixScaleY(); } if (firstScale) { scaleX = detector.getCurrentSpanX(); scaleY = detector.getCurrentSpanY(); firstScale = false; } if (getMatrixScaleY() * scaleFactor < 0.5) { scaleFactor = 0.5f / getMatrixScaleY(); } matrix.postScale(scaleFactor, scaleFactor, scaleX, scaleY); invalidate(); return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { isScaling = false; firstScale = true; } }); boolean isOnClick; GestureDetector gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapConfirmed(MotionEvent e) { isOnClick = true; int x = (int) e.getX(); int y = (int) e.getY(); for (int i = 0; i < row; i++) { for (int j = 0; j < column; j++) { int tempX = (int) ((j * seatBitmap.getWidth() + j * spacing) * getMatrixScaleX() + getTranslateX()); int maxTemX = (int) (tempX + seatBitmap.getWidth() * getMatrixScaleX()); int tempY = (int) ((i * seatBitmap.getHeight() + i * verSpacing) * getMatrixScaleY() + getTranslateY()); int maxTempY = (int) (tempY + seatBitmap.getHeight() * getMatrixScaleY()); if (seatChecker != null && seatChecker.isValidSeat(i, j) && !seatChecker.isSold(i, j)) { if (x >= tempX && x <= maxTemX && y >= tempY && y <= maxTempY) { String seat = i + "," + j; if (isHave(seat)) { remove(seat); if (seatChecker != null) { seatChecker.unCheck(i, j); } } else { if(selects.size()>=maxSelected){ Toast.makeText(getContext(),"最多只能选择"+maxSelected+"个",Toast.LENGTH_SHORT).show(); return super.onSingleTapConfirmed(e); }else { selects.add(seat); if (seatChecker != null) { seatChecker.checked(i, j); } } } isNeedDrawSeatBitmap = true; isDrawOverviewBitmap = true; float currentScaleY = getMatrixScaleY(); if (currentScaleY < 1.7) { postDelayed(new AnimationScaleRunnable(x, y, 1.9f), SCALE_TIME); } invalidate(); break; } } } } return super.onSingleTapConfirmed(e); } }); public MainActivity(Context context) { super(context); } int overview_checked; int overview_sold; int seatCheckedResID; int seatSoldResID; int seatAvailableResID; public MainActivity(Context context, AttributeSet attrs) { super(context, attrs); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SeatTableView); overview_checked = typedArray.getColor(R.styleable.SeatTableView_overview_checked, Color.parseColor("#5A9E64")); overview_sold = typedArray.getColor(R.styleable.SeatTableView_overview_sold, Color.RED); seatCheckedResID = typedArray.getResourceId(R.styleable.SeatTableView_seat_checked, R.drawable.seat1); seatSoldResID = typedArray.getResourceId(R.styleable.SeatTableView_overview_sold, R.drawable.seat2); seatAvailableResID = typedArray.getResourceId(R.styleable.SeatTableView_seat_available, R.drawable.seat); init(); } public MainActivity(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } private void init() { spacing = (int) dip2Px(5); verSpacing = (int) dip2Px(10); defaultScreenWidth = (int) dip2Px(80); seatBitmap = BitmapFactory.decodeResource(getResources(), seatAvailableResID); checkedSeatBitmap = BitmapFactory.decodeResource(getResources(), seatCheckedResID); seatSoldBitmap = BitmapFactory.decodeResource(getResources(), seatSoldResID); seatBitmapWidth = column * seatBitmap.getWidth() + (column - 1) * spacing; seatBitmapHeight = row * seatBitmap.getHeight() + (row - 1) * verSpacing; paint.setColor(Color.RED); numberWidth = (int) dip2Px(20); screenHeight = dip2Px(20); headHeight = dip2Px(30); if (seatBitmapWidth > 0 && seatBitmapHeight > 0) { seat = Bitmap.createBitmap(seatBitmapWidth, seatBitmapHeight, Bitmap.Config.ARGB_4444); seatCanvas = new Canvas(seat); } headPaint = new Paint(); headPaint.setStyle(Paint.Style.FILL); headPaint.setTextSize(24); headPaint.setColor(Color.WHITE); headPaint.setAntiAlias(true); pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG); pathPaint.setStyle(Paint.Style.FILL); pathPaint.setColor(Color.parseColor("#e2e2e2")); redBorderPaint = new Paint(); redBorderPaint.setAntiAlias(true); redBorderPaint.setColor(Color.RED); redBorderPaint.setStyle(Paint.Style.STROKE); redBorderPaint.setStrokeWidth(getResources().getDisplayMetrics().density * 1); rectF = new RectF(); rectSize = seatBitmap.getHeight() / overviewScale; rectWidth = seatBitmap.getWidth() / overviewScale; overviewSpacing = spacing / overviewScale; overviewVerSpacing = verSpacing / overviewScale; rectW = column * rectWidth + (column - 1) * overviewSpacing + overviewSpacing * 2; rectH = row * rectSize + (row - 1) * overviewVerSpacing + overviewVerSpacing * 2; overviewBitmap = Bitmap.createBitmap((int) rectW, (int) rectH, Bitmap.Config.ARGB_4444); } float rectW; float rectH; Paint headPaint; Bitmap headBitmap; boolean isFirstDraw = true; boolean isDrawOverview = false; boolean isDrawOverviewBitmap = true; @Override protected void onDraw(Canvas canvas) { if (row <= 0 || column == 0) { return; } if (isNeedDrawSeatBitmap) { drawSeat(); } if (isFirstDraw) { isFirstDraw = false; matrix.postTranslate(getWidth() / 2 - seatBitmapWidth / 2, headHeight + screenHeight + borderHeight + verSpacing); } canvas.drawBitmap(seat, matrix, paint); drawNumber(canvas); if (headBitmap == null) { headBitmap = drawHeadInfo(); } canvas.drawBitmap(headBitmap, 0, 0, null); drawScreen(canvas); if (isDrawOverview) { if (isDrawOverviewBitmap) { drawOverview(); } canvas.drawBitmap(overviewBitmap, 0, 0, null); drawOverview(canvas); } } private int downX, downY; private boolean pointer; @Override public boolean onTouchEvent(MotionEvent event) { int y = (int) event.getY(); int x = (int) event.getX(); super.onTouchEvent(event); scaleGestureDetector.onTouchEvent(event); gestureDetector.onTouchEvent(event); int pointerCount = event.getPointerCount(); if (pointerCount > 1) { pointer = true; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: pointer = false; downX = x; downY = y; isDrawOverview = true; handler.removeCallbacks(hideOverviewRunnable); invalidate(); break; case MotionEvent.ACTION_MOVE: if (!isScaling && !isOnClick) { int downDX = Math.abs(x - downX); int downDY = Math.abs(y - downY); if ((downDX > 10 || downDY > 10) && !pointer) { int dx = x - lastX; int dy = y - lastY; matrix.postTranslate(dx, dy); invalidate(); } } break; case MotionEvent.ACTION_UP: handler.postDelayed(hideOverviewRunnable, 1500); autoScale(); int downDX = Math.abs(x - downX); int downDY = Math.abs(y - downY); if ((downDX > 10 || downDY > 10) && !pointer) { autoScroll(); } break; } isOnClick = false; lastY = y; lastX = x; return true; } private Runnable hideOverviewRunnable = new Runnable() { @Override public void run() { isDrawOverview = false; invalidate(); } }; float headHeight; int borderHeight = 1; Bitmap drawHeadInfo() { String txt = "已售"; float txtY = getBaseLine(headPaint, 0, headHeight); int txtWidth = (int) headPaint.measureText(txt); float spacing = dip2Px(10); float spacing1 = dip2Px(5); float y = (headHeight - seatBitmap.getHeight()) / 2; float width = seatBitmap.getWidth() + spacing1 + txtWidth + spacing + seatSoldBitmap.getWidth() + txtWidth + spacing1 + spacing + checkedSeatBitmap.getHeight() + spacing1 + txtWidth; Bitmap bitmap = Bitmap.createBitmap(getWidth(), (int) headHeight, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); //绘制背景 canvas.drawRect(0, 0, getWidth(), headHeight, headPaint); headPaint.setColor(Color.BLACK); float startX = (getWidth() - width) / 2; canvas.drawBitmap(seatBitmap, startX, (headHeight - seatBitmap.getHeight()) / 2, headPaint); canvas.drawText("可选", startX + seatBitmap.getWidth() + spacing1, txtY, headPaint); float soldSeatBitmapY = startX + seatBitmap.getWidth() + spacing1 + txtWidth + spacing; canvas.drawBitmap(seatSoldBitmap, soldSeatBitmapY, (headHeight - seatBitmap.getHeight()) / 2, headPaint); canvas.drawText("已售", soldSeatBitmapY + seatSoldBitmap.getWidth() + spacing1, txtY, headPaint); float checkedSeatBitmapX = soldSeatBitmapY + seatSoldBitmap.getWidth() + spacing1 + txtWidth + spacing; canvas.drawBitmap(checkedSeatBitmap, checkedSeatBitmapX, y, headPaint); canvas.drawText("已选", checkedSeatBitmapX + spacing1 + checkedSeatBitmap.getWidth(), txtY, headPaint); //绘制分割线 headPaint.setStrokeWidth(1); headPaint.setColor(Color.GRAY); canvas.drawLine(0, headHeight, getWidth(), headHeight, headPaint); return bitmap; } Paint pathPaint; /** * 绘制中间屏幕 */ void drawScreen(Canvas canvas) { pathPaint.setStyle(Paint.Style.FILL); pathPaint.setColor(Color.parseColor("#e2e2e2")); float startY = headHeight + borderHeight; float centerX = seatBitmapWidth * getMatrixScaleX() / 2 + getTranslateX(); float screenWidth = seatBitmapWidth * screenWidthScale * getMatrixScaleX(); if (screenWidth < defaultScreenWidth) { screenWidth = defaultScreenWidth; } Path path = new Path(); path.moveTo(centerX, startY); path.lineTo(centerX - screenWidth / 2, startY); path.lineTo(centerX - screenWidth / 2 + 20, screenHeight * getMatrixScaleY() + startY); path.lineTo(centerX + screenWidth / 2 - 20, screenHeight * getMatrixScaleY() + startY); path.lineTo(centerX + screenWidth / 2, startY); canvas.drawPath(path, pathPaint); pathPaint.setColor(Color.BLACK); pathPaint.setTextSize(20 * getMatrixScaleX()); canvas.drawText(screenName, centerX - pathPaint.measureText(screenName) / 2, getBaseLine(pathPaint, startY, startY + screenHeight * getMatrixScaleY()), pathPaint); } private static final int SEAT_TYPE_SOLD = 1; private static final int SEAT_TYPE_SELECTED = 2; private static final int SEAT_TYPE_AVAILABLE = 3; private static final int SEAT_TYPE_NOT_AVAILABLE = 4; Canvas seatCanvas; void drawSeat() { for (int i = 0; i < row; i++) { for (int j = 0; j < column; j++) { int left = j * seatBitmap.getWidth() + j * spacing; int top = i * seatBitmap.getHeight() + i * verSpacing; int seatType = getSeatType(i, j); switch (seatType) { case SEAT_TYPE_AVAILABLE: seatCanvas.drawBitmap(seatBitmap, left, top, paint); break; case SEAT_TYPE_NOT_AVAILABLE: break; case SEAT_TYPE_SELECTED: seatCanvas.drawBitmap(checkedSeatBitmap, left, top, paint); break; case SEAT_TYPE_SOLD: seatCanvas.drawBitmap(seatSoldBitmap, left, top, paint); break; } } } isNeedDrawSeatBitmap = false; } private int getSeatType(int row, int column) { String seat = row + "," + column; int seatType = SEAT_TYPE_AVAILABLE; if (seatChecker != null) { if (!seatChecker.isValidSeat(row, column)) { seatType = SEAT_TYPE_NOT_AVAILABLE; } else if (seatChecker.isSold(row, column)) { seatType = SEAT_TYPE_SOLD; } } if (isHave(seat)) { seatType = SEAT_TYPE_SELECTED; } return seatType; } RectF rectF; /** * 绘制行号 */ void drawNumber(Canvas canvas) { paint.setColor(Color.parseColor("#7e000000")); paint.setTextSize(getResources().getDisplayMetrics().density * 16); paint.setAntiAlias(true); int translateY = (int) getTranslateY(); float scaleY = getMatrixScaleY(); rectF.top = translateY - paint.measureText("4") / 2; rectF.bottom = translateY + (seatBitmapHeight * scaleY) + paint.measureText("4") / 2; rectF.left = 0; rectF.right = numberWidth; canvas.drawRoundRect(rectF, numberWidth / 2, numberWidth / 2, paint); Paint.FontMetrics fontMetrics = paint.getFontMetrics(); paint.setColor(Color.WHITE); paint.setTextAlign(Paint.Align.CENTER); for (int i = 0; i < row; i++) { int top = (int) ((i * seatBitmap.getHeight() + i * verSpacing) * scaleY) + translateY; int bottom = (int) ((i * seatBitmap.getHeight() + i * verSpacing + seatBitmap.getHeight()) * scaleY) + translateY; int baseline = (int) ((bottom + top - fontMetrics.bottom - fontMetrics.top) / 2); canvas.drawText((i + 1) + "", numberWidth / 2, baseline, paint); } } Paint redBorderPaint; /** * 绘制概览图 */ void drawOverview(Canvas canvas) { //绘制红色框 int left = (int) -getTranslateX(); if (left < 0) { left = 0; } left /= overviewScale; left /= getMatrixScaleX(); int currentWidth = (int) (getTranslateX() + (column * seatBitmap.getWidth() + spacing * (column - 1)) * getMatrixScaleX()); if (currentWidth > getWidth()) { currentWidth = currentWidth - getWidth(); } else { currentWidth = 0; } int right = (int) (rectW - currentWidth / overviewScale / getMatrixScaleX()); float top = -getTranslateY()+headHeight; if (top < 0) { top = 0; } top /= overviewScale; top /= getMatrixScaleY(); if (top > 0) { top += overviewVerSpacing; } int currentHeight = (int) (getTranslateY() + (row * seatBitmap.getHeight() + verSpacing * (row - 1)) * getMatrixScaleY()); if (currentHeight > getHeight()) { currentHeight = currentHeight - getHeight(); } else { currentHeight = 0; } int bottom = (int) (rectH - currentHeight / overviewScale / getMatrixScaleY()); canvas.drawRect(left, top, right, bottom, redBorderPaint); } Bitmap drawOverview() { isDrawOverviewBitmap = false; int bac = Color.parseColor("#7e000000"); paint.setColor(bac); paint.setAntiAlias(true); paint.setStyle(Paint.Style.FILL); Canvas canvas = new Canvas(overviewBitmap); //绘制透明灰色背景 canvas.drawRect(0, 0, rectW, rectH, paint); paint.setColor(Color.WHITE); for (int i = 0; i < row; i++) { float top = i * rectSize + i * overviewVerSpacing + overviewVerSpacing; for (int j = 0; j < column; j++) { int seatType = getSeatType(i, j); switch (seatType) { case SEAT_TYPE_AVAILABLE: paint.setColor(Color.WHITE); break; case SEAT_TYPE_NOT_AVAILABLE: continue; case SEAT_TYPE_SELECTED: paint.setColor(overview_checked); break; case SEAT_TYPE_SOLD: paint.setColor(overview_sold); break; } float left; left = j * rectWidth + j * overviewSpacing + overviewSpacing; canvas.drawRect(left, top, left + rectWidth, top + rectSize, paint); } } return overviewBitmap; } /** * 自动回弹 * 整个大小不超过控件大小的时候: * 往左边滑动,自动回弹到行号右边 * 往右边滑动,自动回弹到右边 * 往上,下滑动,自动回弹到顶部 * <p> * 整个大小超过控件大小的时候: * 往左侧滑动,回弹到最右边,往右侧滑回弹到最左边 * 往上滑动,回弹到底部,往下滑动回弹到顶部 */ private void autoScroll() { float currentSeatBitmapWidth = seatBitmapWidth * getMatrixScaleX(); float currentSeatBitmapHeight = seatBitmapHeight * getMatrixScaleY(); float moveYLength = 0; float moveXLength = 0; //处理左右滑动的情况 if (currentSeatBitmapWidth < getWidth()) { if (getTranslateX() < 0 || getMatrixScaleX() < numberWidth + spacing) { //计算要移动的距离 if (getTranslateX() < 0) { moveXLength = (-getTranslateX()) + numberWidth + spacing; } else { moveXLength = numberWidth + spacing - getTranslateX(); } } } else { if (getTranslateX() < 0 && getTranslateX() + currentSeatBitmapWidth > getWidth()) { } else { //往左侧滑动 if (getTranslateX() + currentSeatBitmapWidth < getWidth()) { moveXLength = getWidth() - (getTranslateX() + currentSeatBitmapWidth); } else { //右侧滑动 moveXLength = -getTranslateX() + numberWidth + spacing; } } } float startYPosition = screenHeight * getMatrixScaleY() + verSpacing * getMatrixScaleY() + headHeight + borderHeight; //处理上下滑动 if (currentSeatBitmapHeight < getHeight()) { if (getTranslateY() < startYPosition) { moveYLength = startYPosition - getTranslateY(); } else { moveYLength = -(getTranslateY() - (startYPosition)); } } else { if (getTranslateY() < 0 && getTranslateY() + currentSeatBitmapHeight > getHeight()) { } else { //往上滑动 if (getTranslateY() + currentSeatBitmapHeight < getHeight()) { moveYLength = getHeight() - (getTranslateY() + currentSeatBitmapHeight); } else { moveYLength = -(getTranslateY() - (startYPosition)); } } } Message message = Message.obtain(); MoveInfo moveInfo = new MoveInfo(); moveInfo.moveXLength = moveXLength; moveInfo.moveYLength = moveYLength; message.obj = moveInfo; handler.sendMessageDelayed(message, time); } class MoveInfo { public float moveXLength; public float moveYLength; } private void autoScale() { if (getMatrixScaleX() > 2.2) { postDelayed(new AnimationScaleRunnable(scaleX, scaleY, 2.0f), SCALE_TIME); } else if (getMatrixScaleX() < 0.98) { postDelayed(new AnimationScaleRunnable(scaleX, scaleY, 1.0f), SCALE_TIME); } } int FRAME_COUNT = 10; int time = 15; int count; int SCALE_TIME = 15; Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (count < FRAME_COUNT) { count++; MoveInfo moveInfo = (MoveInfo) msg.obj; float moveXLength = moveInfo.moveXLength; float moveYLength = moveInfo.moveYLength; float xValue = moveXLength / FRAME_COUNT; float yValue = moveYLength / FRAME_COUNT; matrix.postTranslate(xValue, yValue); invalidate(); Message message = Message.obtain(); message.obj = msg.obj; handler.sendMessageDelayed(message, time); } else { count = 0; } return true; } }); ArrayList<String> selects = new ArrayList<>(); public ArrayList<String> getSelectedSeats() { return selects; } private boolean isHave(String seat) { for (String item : selects) { if (seat.equals(item)) { return true; } } return false; } private void remove(String seat) { for (int i = 0; i < selects.size(); i++) { String item = selects.get(i); if (seat.equals(item)) { selects.remove(i); break; } } } float[] m = new float[9]; private float getTranslateX() { matrix.getValues(m); return m[2]; } private float getTranslateY() { float[] m = new float[9]; matrix.getValues(m); return m[5]; } private float getMatrixScaleY() { matrix.getValues(m); return m[4]; } private float getMatrixScaleX() { matrix.getValues(m); return m[0]; } private float dip2Px(float value) { return getResources().getDisplayMetrics().density * value; } private float getBaseLine(Paint p, float top, float bottom) { Paint.FontMetrics fontMetrics = p.getFontMetrics(); int baseline = (int) ((bottom + top - fontMetrics.bottom - fontMetrics.top) / 2); return baseline; } public class AnimationScaleRunnable implements Runnable { private float x, y;//缩放的中心点 private float targetScale; private float tempScale; public AnimationScaleRunnable(float x, float y, float targetScale) { float currentScale = getMatrixScaleX(); this.x = x; this.y = y; this.targetScale = targetScale; if (currentScale < targetScale) { tempScale = 1.06f; } else { tempScale = 0.95f; } } @Override public void run() { matrix.postScale(tempScale, tempScale, x, y); invalidate(); float currentScale = getMatrixScaleX(); if (tempScale > 1 && currentScale < targetScale) { postDelayed(this, SCALE_TIME); } else if (tempScale < 1 && currentScale > targetScale) { postDelayed(this, SCALE_TIME); } } } public void setData(int row, int column) { this.row = row; this.column = column; init(); invalidate(); } public interface SeatChecker { /** * 是否可用座位 * * @param row * @param column * @return */ boolean isValidSeat(int row, int column); /** * 是否已售 * * @param row * @param column * @return */ boolean isSold(int row, int column); void checked(int row, int column); void unCheck(int row, int column); }}