[Taxi-d 280, Taxi-p 130] opt
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
package com.mogo.och.common.module.wigets.sfv;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
import com.elegant.utils.UiThreadHandler;
|
||||
|
||||
public abstract class BaseSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
|
||||
public static final int DEFAULT_FRAME_DURATION_MILLISECOND = 50;
|
||||
|
||||
private HandlerThread handlerThread;
|
||||
private SurfaceViewHandler handler;
|
||||
protected int frameDuration = DEFAULT_FRAME_DURATION_MILLISECOND;
|
||||
private Canvas canvas;
|
||||
private boolean isAlive;
|
||||
|
||||
public BaseSurfaceView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public BaseSurfaceView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public BaseSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
protected int getFrameDuration() {
|
||||
return frameDuration;
|
||||
}
|
||||
|
||||
protected void setFrameDuration(int frameDuration) {
|
||||
this.frameDuration = frameDuration;
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
getHolder().addCallback(this);
|
||||
setBackgroundTransparent();
|
||||
}
|
||||
|
||||
private void setBackgroundTransparent() {
|
||||
getHolder().setFormat(PixelFormat.TRANSLUCENT);
|
||||
setZOrderOnTop(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
isAlive = true;
|
||||
startDrawThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
stopDrawThread();
|
||||
isAlive = false;
|
||||
}
|
||||
|
||||
private void stopDrawThread() {
|
||||
isAlive = false;
|
||||
handlerThread.quit();
|
||||
handler = null;
|
||||
}
|
||||
|
||||
private void startDrawThread() {
|
||||
handlerThread = new HandlerThread("SurfaceViewThread");
|
||||
handlerThread.start();
|
||||
handler = new SurfaceViewHandler(handlerThread.getLooper());
|
||||
handler.post(new DrawRunnable());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
int originWidth = getMeasuredWidth();
|
||||
int originHeight = getMeasuredHeight();
|
||||
int width = widthMode == MeasureSpec.AT_MOST ? getDefaultWidth() : originWidth;
|
||||
int height = heightMode == MeasureSpec.AT_MOST ? getDefaultHeight() : originHeight;
|
||||
setMeasuredDimension(width, height);
|
||||
Log.v("ttaylor", "BaseSurfaceView.onMeasure()" + " default Width=" + getDefaultWidth() + " default height=" + getDefaultHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* the width is used when wrap_content is set to layout_width
|
||||
* the child knows how big it should be
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected abstract int getDefaultWidth();
|
||||
|
||||
/**
|
||||
* the height is used when wrap_content is set to layout_height
|
||||
* the child knows how big it should be
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected abstract int getDefaultHeight();
|
||||
|
||||
|
||||
private class SurfaceViewHandler extends Handler {
|
||||
|
||||
public SurfaceViewHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
super.handleMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private class DrawRunnable implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isAlive) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
canvas = getHolder().lockCanvas();
|
||||
onFrameDraw(canvas);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
getHolder().unlockCanvasAndPost(canvas);
|
||||
onFrameDrawFinish();
|
||||
}
|
||||
|
||||
handler.postDelayed(this, frameDuration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* it is will be invoked after one frame is drawn
|
||||
*/
|
||||
protected abstract void onFrameDrawFinish();
|
||||
|
||||
/**
|
||||
* draw one frame to the surface by canvas
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
protected abstract void onFrameDraw(Canvas canvas);
|
||||
|
||||
protected void runOnUIThread( Runnable executor ) {
|
||||
if ( executor == null ) {
|
||||
return;
|
||||
}
|
||||
if ( Looper.myLooper() != Looper.getMainLooper() ) {
|
||||
UiThreadHandler.post( executor );
|
||||
} else {
|
||||
executor.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.mogo.och.common.module.wigets.sfv;
|
||||
|
||||
public interface FrameFinishCallback {
|
||||
void onFinishCallback();
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
package com.mogo.och.common.module.wigets.sfv;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* a SurfaceView which draws bitmaps one after another like frame animation
|
||||
*/
|
||||
public class FrameSurfaceView extends BaseSurfaceView {
|
||||
public static final int INVALID_INDEX = Integer.MAX_VALUE;
|
||||
private int bufferSize = 3;
|
||||
public static final String DECODE_THREAD_NAME = "DecodingThread";
|
||||
public static final int INFINITE = -1;
|
||||
//-1 means repeat infinitely
|
||||
private int repeatTimes;
|
||||
private int repeatedCount;
|
||||
|
||||
/**
|
||||
* the resources of frame animation
|
||||
*/
|
||||
private List<Integer> bitmapIds = new ArrayList<>();
|
||||
/**
|
||||
* the index of bitmap resource which is decoding
|
||||
*/
|
||||
private int bitmapIdIndex;
|
||||
/**
|
||||
* the index of frame which is drawing
|
||||
*/
|
||||
private AtomicInteger frameIndex;
|
||||
/**
|
||||
* decoded bitmaps stores in this queue
|
||||
* consumer is drawing thread, producer is decoding thread.
|
||||
*/
|
||||
private LinkedBlockingQueue decodedBitmaps = new LinkedBlockingQueue(bufferSize);
|
||||
/**
|
||||
* bitmaps already drawn by canvas stores in this queue
|
||||
* consumer is decoding thread, producer is drawing thread.
|
||||
*/
|
||||
private LinkedBlockingQueue drawnBitmaps = new LinkedBlockingQueue(bufferSize);
|
||||
/**
|
||||
* the thread for decoding bitmaps
|
||||
*/
|
||||
private HandlerThread decodeThread;
|
||||
/**
|
||||
* the Runnable describes how to decode one bitmap
|
||||
*/
|
||||
private DecodeRunnable decodeRunnable;
|
||||
/**
|
||||
* this handler helps to decode bitmap one after another
|
||||
*/
|
||||
private Handler handler;
|
||||
private BitmapFactory.Options options;
|
||||
private Paint paint = new Paint();
|
||||
private Rect srcRect;
|
||||
private Rect dstRect = new Rect();
|
||||
private int defaultWidth;
|
||||
private int defaultHeight;
|
||||
|
||||
private FrameFinishCallback frameFinishCallback;
|
||||
|
||||
public FrameSurfaceView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public FrameSurfaceView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public FrameSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setRepeatTimes(int repeatTimes) {
|
||||
this.repeatTimes = repeatTimes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
options = new BitmapFactory.Options();
|
||||
options.inMutable = true;
|
||||
decodeThread = new HandlerThread(DECODE_THREAD_NAME);
|
||||
frameIndex = new AtomicInteger();
|
||||
frameIndex.set(INVALID_INDEX);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
dstRect.set(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultWidth() {
|
||||
return defaultWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultHeight() {
|
||||
return defaultHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFrameDrawFinish() {
|
||||
}
|
||||
|
||||
/**
|
||||
* set the duration of frame animation
|
||||
*
|
||||
* @param duration time in milliseconds
|
||||
*/
|
||||
public void setDuration(int duration) {
|
||||
int frameDuration = duration / bitmapIds.size();
|
||||
setFrameDuration(frameDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* set the materials of frame animation which is an array of bitmap resource id
|
||||
*
|
||||
* @param bitmapIds an array of bitmap resource id
|
||||
*/
|
||||
public void setBitmapIds(List<Integer> bitmapIds) {
|
||||
if (bitmapIds == null || bitmapIds.size() == 0) {
|
||||
return;
|
||||
}
|
||||
this.bitmapIds = bitmapIds;
|
||||
//by default, take the first bitmap's dimension into consideration
|
||||
getBitmapDimension(bitmapIds.get(bitmapIdIndex));
|
||||
preloadFrames();
|
||||
decodeRunnable = new DecodeRunnable(bitmapIdIndex, bitmapIds, options);
|
||||
}
|
||||
|
||||
private void getBitmapDimension(int bitmapId) {
|
||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeResource(this.getResources(), bitmapId, options);
|
||||
defaultWidth = options.outWidth;
|
||||
defaultHeight = options.outHeight;
|
||||
srcRect = new Rect(0, 0, defaultWidth, defaultHeight);
|
||||
//we have to re-measure to make defaultWidth in use in onMeasure()
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* load the first several frames of animation before it is started
|
||||
*/
|
||||
private void preloadFrames() {
|
||||
decodeAndPutBitmap(bitmapIds.get(bitmapIdIndex++), options, new LinkedBitmap());
|
||||
decodeAndPutBitmap(bitmapIds.get(bitmapIdIndex++), options, new LinkedBitmap());
|
||||
}
|
||||
|
||||
/**
|
||||
* recycle the bitmap used by frame animation.
|
||||
* Usually it should be invoked when the ui of frame animation is no longer visible
|
||||
*/
|
||||
public void destroy() {
|
||||
if (drawnBitmaps != null) {
|
||||
drawnBitmaps.clear();
|
||||
}
|
||||
if (decodeThread != null) {
|
||||
decodeThread.quit();
|
||||
decodeThread = null;
|
||||
}
|
||||
if (handler != null) {
|
||||
handler = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFrameDraw(Canvas canvas) {
|
||||
clearCanvas(canvas);
|
||||
if (!isStart()) {
|
||||
return;
|
||||
}
|
||||
if (!isFinish()) {
|
||||
drawOneFrame(canvas);
|
||||
} else {
|
||||
onFrameAnimationEnd();
|
||||
if (repeatTimes != 0 && repeatTimes == INFINITE) {
|
||||
start();
|
||||
} else if (repeatedCount < repeatTimes) {
|
||||
start();
|
||||
repeatedCount++;
|
||||
} else {
|
||||
repeatedCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* draw a single frame which is a bitmap
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawOneFrame(Canvas canvas) {
|
||||
LinkedBitmap linkedBitmap = getDecodedBitmap();
|
||||
if (linkedBitmap != null) {
|
||||
canvas.drawBitmap(linkedBitmap.bitmap, srcRect, dstRect, paint);
|
||||
}
|
||||
putDrawnBitmap(linkedBitmap);
|
||||
frameIndex.incrementAndGet();
|
||||
if(isFinish()&&frameFinishCallback!=null){
|
||||
runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
frameFinishCallback.onFinishCallback();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* invoked when frame animation is done
|
||||
*/
|
||||
private void onFrameAnimationEnd() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* reset the index of frame, preparing for the next frame animation
|
||||
*/
|
||||
public void reset() {
|
||||
frameIndex.set(INVALID_INDEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* whether frame animation is finished
|
||||
*
|
||||
* @return true: animation is finished, false: animation is doing
|
||||
*/
|
||||
private boolean isFinish() {
|
||||
return frameIndex.get() >= bitmapIds.size() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* whether frame animation is started
|
||||
*
|
||||
* @return true: animation is started, false: animation is not started
|
||||
*/
|
||||
private boolean isStart() {
|
||||
return frameIndex.get() != INVALID_INDEX;
|
||||
}
|
||||
|
||||
/**
|
||||
* start frame animation from the first frame
|
||||
*/
|
||||
public void start() {
|
||||
frameIndex.compareAndSet(INVALID_INDEX, 0);
|
||||
if (decodeThread == null) {
|
||||
decodeThread = new HandlerThread(DECODE_THREAD_NAME);
|
||||
}
|
||||
if (!decodeThread.isAlive()) {
|
||||
decodeThread.start();
|
||||
}
|
||||
if (handler == null) {
|
||||
handler = new Handler(decodeThread.getLooper());
|
||||
}
|
||||
if (decodeRunnable != null) {
|
||||
decodeRunnable.setIndex(0);
|
||||
}
|
||||
handler.post(decodeRunnable);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* clear out the drawing on canvas,preparing for the next frame
|
||||
* * @param canvas
|
||||
*/
|
||||
private void clearCanvas(Canvas canvas) {
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
canvas.drawPaint(paint);
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
|
||||
}
|
||||
|
||||
/**
|
||||
* decode bitmap by BitmapFactory.decodeStream(), it is about twice faster than BitmapFactory.decodeResource()
|
||||
*
|
||||
* @param resId the bitmap resource
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
private Bitmap decodeBitmap(int resId, BitmapFactory.Options options) {
|
||||
options.inScaled = false;
|
||||
InputStream inputStream = getResources().openRawResource(resId);
|
||||
return BitmapFactory.decodeStream(inputStream, null, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* reuse bitmap in drawnBitmaps to decode new bitmap
|
||||
*
|
||||
* @param resId
|
||||
* @param options
|
||||
*/
|
||||
private void decodedBitmapByReuse(int resId, BitmapFactory.Options options) {
|
||||
LinkedBitmap linkedBitmap = getDrawnBitmap();
|
||||
if (linkedBitmap == null) {
|
||||
linkedBitmap = new LinkedBitmap();
|
||||
}
|
||||
options.inBitmap = linkedBitmap.bitmap;
|
||||
decodeAndPutBitmap(resId, options, linkedBitmap);
|
||||
}
|
||||
|
||||
/**
|
||||
* decode bitmap and put it into decodedBitmaps
|
||||
*
|
||||
* @param resId
|
||||
* @param options
|
||||
* @param linkedBitmap
|
||||
*/
|
||||
private void decodeAndPutBitmap(int resId, BitmapFactory.Options options, LinkedBitmap linkedBitmap) {
|
||||
Bitmap bitmap = decodeBitmap(resId, options);
|
||||
linkedBitmap.bitmap = bitmap;
|
||||
try {
|
||||
decodedBitmaps.put(linkedBitmap);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void putDrawnBitmap(LinkedBitmap bitmap) {
|
||||
drawnBitmaps.offer(bitmap);
|
||||
}
|
||||
|
||||
/**
|
||||
* get bitmap which already drawn by canvas
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private LinkedBitmap getDrawnBitmap() {
|
||||
LinkedBitmap bitmap = null;
|
||||
try {
|
||||
bitmap = drawnBitmaps.take();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* get decoded bitmap in the decoded bitmap queue
|
||||
* it might block due to new bitmap is not ready
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private LinkedBitmap getDecodedBitmap() {
|
||||
LinkedBitmap bitmap = null;
|
||||
try {
|
||||
bitmap = decodedBitmaps.take();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public FrameFinishCallback getFrameFinishCallback() {
|
||||
return frameFinishCallback;
|
||||
}
|
||||
|
||||
public void setFrameFinishCallback(FrameFinishCallback frameFinishCallback) {
|
||||
this.frameFinishCallback = frameFinishCallback;
|
||||
}
|
||||
|
||||
private class DecodeRunnable implements Runnable {
|
||||
|
||||
private int index;
|
||||
private List<Integer> bitmapIds;
|
||||
private BitmapFactory.Options options;
|
||||
|
||||
public DecodeRunnable(int index, List<Integer> bitmapIds, BitmapFactory.Options options) {
|
||||
this.index = index;
|
||||
this.bitmapIds = bitmapIds;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
decodedBitmapByReuse(bitmapIds.get(index), options);
|
||||
index++;
|
||||
if (index < bitmapIds.size()) {
|
||||
handler.post(this);
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.mogo.och.common.module.wigets.sfv;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
/**
|
||||
* a structure used by LinkedBlockingQueue to keep bitmap
|
||||
*/
|
||||
public class LinkedBitmap {
|
||||
public Bitmap bitmap;
|
||||
public LinkedBitmap next;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.mogo.och.common.module.wigets.sfv;
|
||||
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class LinkedBlockingQueue {
|
||||
/**
|
||||
* Current number of elements
|
||||
*/
|
||||
private final AtomicInteger count = new AtomicInteger();
|
||||
/**
|
||||
* Lock held by take, poll, etc
|
||||
*/
|
||||
private final ReentrantLock takeLock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* Wait queue for waiting takes
|
||||
*/
|
||||
private final Condition notEmpty = takeLock.newCondition();
|
||||
|
||||
/**
|
||||
* Lock held by put, offer, etc
|
||||
*/
|
||||
private final ReentrantLock putLock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* Wait queue for waiting puts
|
||||
*/
|
||||
private final Condition notFull = putLock.newCondition();
|
||||
/**
|
||||
* The capacity bound, or Integer.MAX_VALUE if none
|
||||
*/
|
||||
private final int capacity;
|
||||
/**
|
||||
* the first element in the queue
|
||||
*/
|
||||
private LinkedBitmap head;
|
||||
/**
|
||||
* the last element int the queue
|
||||
*/
|
||||
private LinkedBitmap tail;
|
||||
|
||||
|
||||
public LinkedBlockingQueue(int capacity) {
|
||||
if (capacity <= 0) throw new IllegalArgumentException();
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public void put(LinkedBitmap bitmap) throws InterruptedException {
|
||||
if (bitmap == null) throw new NullPointerException();
|
||||
// Note: convention in all put/take/etc is to preset local var
|
||||
// holding count negative to indicate failure unless set.
|
||||
int c = -1;
|
||||
final ReentrantLock putLock = this.putLock;
|
||||
final AtomicInteger count = this.count;
|
||||
putLock.lockInterruptibly();
|
||||
try {
|
||||
/*
|
||||
* Note that count is used in wait guard even though it is
|
||||
* not protected by lock. This works because count can
|
||||
* only decrease at this point (all other puts are shut
|
||||
* out by lock), and we (or some other waiting put) are
|
||||
* signalled if it ever changes from capacity. Similarly
|
||||
* for all other uses of count in other wait guards.
|
||||
*/
|
||||
while (count.get() == capacity) {
|
||||
notFull.await();
|
||||
}
|
||||
enqueue(bitmap);
|
||||
c = count.getAndIncrement();
|
||||
if (c + 1 < capacity)
|
||||
notFull.signal();
|
||||
} finally {
|
||||
putLock.unlock();
|
||||
}
|
||||
if (c == 0)
|
||||
signalNotEmpty();
|
||||
}
|
||||
|
||||
public boolean offer(LinkedBitmap bitmap) {
|
||||
if (bitmap == null) throw new NullPointerException();
|
||||
final AtomicInteger count = this.count;
|
||||
if (count.get() == capacity)
|
||||
return false;
|
||||
int c = -1;
|
||||
final ReentrantLock putLock = this.putLock;
|
||||
putLock.lock();
|
||||
try {
|
||||
if (count.get() < capacity) {
|
||||
enqueue(bitmap);
|
||||
c = count.getAndIncrement();
|
||||
if (c + 1 < capacity)
|
||||
notFull.signal();
|
||||
}
|
||||
} finally {
|
||||
putLock.unlock();
|
||||
}
|
||||
if (c == 0)
|
||||
signalNotEmpty();
|
||||
return c >= 0;
|
||||
}
|
||||
|
||||
public LinkedBitmap take() throws InterruptedException {
|
||||
LinkedBitmap x;
|
||||
int c = -1;
|
||||
final AtomicInteger count = this.count;
|
||||
final ReentrantLock takeLock = this.takeLock;
|
||||
takeLock.lockInterruptibly();
|
||||
try {
|
||||
while (count.get() == 0) {
|
||||
notEmpty.await();
|
||||
}
|
||||
x = dequeue();
|
||||
c = count.getAndDecrement();
|
||||
if (c > 1)
|
||||
notEmpty.signal();
|
||||
} finally {
|
||||
takeLock.unlock();
|
||||
}
|
||||
if (c == capacity)
|
||||
signalNotFull();
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* insert element into the end of queue
|
||||
*
|
||||
* @param bitmap
|
||||
*/
|
||||
private void enqueue(LinkedBitmap bitmap) {
|
||||
if (head == null) {
|
||||
head = bitmap;
|
||||
tail = bitmap;
|
||||
bitmap.next = null;
|
||||
} else {
|
||||
tail.next = bitmap;
|
||||
bitmap.next = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get and remove the first element of the queue
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private LinkedBitmap dequeue() {
|
||||
LinkedBitmap p = head;
|
||||
if (p == null) {
|
||||
return null;
|
||||
} else {
|
||||
head = head.next;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals a waiting take. Called only from put/offer (which do not
|
||||
* otherwise ordinarily lock takeLock.)
|
||||
*/
|
||||
private void signalNotEmpty() {
|
||||
final ReentrantLock takeLock = this.takeLock;
|
||||
takeLock.lock();
|
||||
try {
|
||||
notEmpty.signal();
|
||||
} finally {
|
||||
takeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals a waiting put. Called only from take/poll.
|
||||
*/
|
||||
private void signalNotFull() {
|
||||
final ReentrantLock putLock = this.putLock;
|
||||
putLock.lock();
|
||||
try {
|
||||
notFull.signal();
|
||||
} finally {
|
||||
putLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* recycle the bitmaps one by one
|
||||
*/
|
||||
public void clear() {
|
||||
LinkedBitmap p = head;
|
||||
if (p == null) {
|
||||
return;
|
||||
}
|
||||
while (p != null) {
|
||||
if (p.bitmap != null) {
|
||||
p.bitmap.recycle();
|
||||
}
|
||||
p.bitmap = null;
|
||||
p = p.next;
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getCount() {
|
||||
return count.get();
|
||||
}
|
||||
}
|
||||
@@ -279,20 +279,20 @@ public class TaxiPassengerBaseFragment extends MvpFragment<TaxiPassengerBaseFrag
|
||||
* @param isShow
|
||||
*/
|
||||
public void showOrHideStartAutopilotView(boolean isShow, boolean isClickable){
|
||||
// if (isShow){
|
||||
// if (mStartAutopilotView == null || mStartAutopilotView.get() == null){
|
||||
// mStartAutopilotView = new WeakReference<>(new TaxiPassengerStartAutopilotView(getContext()));
|
||||
// mStartAutopilotView.get().setOnClickStartAutopilotBtnCallback(this);
|
||||
// }
|
||||
// OverlayViewUtils.showOverlayView(getActivity(),mStartAutopilotView.get());
|
||||
// updateStartAutopilotBtnStatus(isClickable);
|
||||
// }else {
|
||||
// if (mStartAutopilotView == null || mStartAutopilotView.get() == null){
|
||||
// return;
|
||||
// }
|
||||
// mStartAutopilotView.get().setOnClickStartAutopilotBtnCallback(null);
|
||||
// OverlayViewUtils.dismissOverlayView(mStartAutopilotView.get());
|
||||
// }
|
||||
if (isShow){
|
||||
if (mStartAutopilotView == null || mStartAutopilotView.get() == null){
|
||||
mStartAutopilotView = new WeakReference<>(new TaxiPassengerStartAutopilotView(getContext()));
|
||||
mStartAutopilotView.get().setOnClickStartAutopilotBtnCallback(this);
|
||||
}
|
||||
OverlayViewUtils.showOverlayView(getActivity(),mStartAutopilotView.get());
|
||||
updateStartAutopilotBtnStatus(isClickable);
|
||||
}else {
|
||||
if (mStartAutopilotView == null || mStartAutopilotView.get() == null){
|
||||
return;
|
||||
}
|
||||
mStartAutopilotView.get().setOnClickStartAutopilotBtnCallback(null);
|
||||
OverlayViewUtils.dismissOverlayView(mStartAutopilotView.get());
|
||||
}
|
||||
}
|
||||
|
||||
public void updateStartAutopilotBtnStatus(boolean isClickable){
|
||||
|
||||
@@ -4,15 +4,18 @@ import android.content.Context;
|
||||
import android.graphics.drawable.AnimationDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.mogo.eagle.core.utilcode.util.UiThreadHandler;
|
||||
import com.elegant.utils.UiThreadHandler;
|
||||
import com.mogo.eagle.core.utilcode.util.ToastUtils;
|
||||
import com.mogo.och.common.module.wigets.sfv.FrameFinishCallback;
|
||||
import com.mogo.och.common.module.wigets.sfv.FrameSurfaceView;
|
||||
import com.mogo.och.taxi.passenger.R;
|
||||
import com.mogo.och.taxi.passenger.callback.ITPClickStartAutopilotCallback;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author: wangmingjun
|
||||
* @date: 2022/6/14
|
||||
@@ -20,7 +23,7 @@ import com.mogo.och.taxi.passenger.callback.ITPClickStartAutopilotCallback;
|
||||
public class TaxiPassengerStartAutopilotView extends RelativeLayout implements View.OnClickListener {
|
||||
|
||||
private TextView mStartAutopilotBtn;
|
||||
private ImageView mAutopilotStartingImage;
|
||||
// private ImageView mAutopilotStartingImage;
|
||||
private ITPClickStartAutopilotCallback mClickCallback;
|
||||
public boolean isStarting = false;
|
||||
private AnimationDrawable mAnimationBtnDrawable;
|
||||
@@ -28,6 +31,101 @@ public class TaxiPassengerStartAutopilotView extends RelativeLayout implements V
|
||||
private static final long TIMER_START_AUTOPILOT_INTERVAL = 10 * 1000L;
|
||||
private Context mContext;
|
||||
private View view;
|
||||
private FrameSurfaceView svCarStartingFrame;
|
||||
private FrameSurfaceView svBtnBgFrame;
|
||||
private Integer[] btnBgAnimIds = new Integer[]{
|
||||
R.drawable.image_00000,
|
||||
R.drawable.image_00001,
|
||||
R.drawable.image_00002,
|
||||
R.drawable.image_00003,
|
||||
R.drawable.image_00004,
|
||||
R.drawable.image_00005,
|
||||
R.drawable.image_00006,
|
||||
R.drawable.image_00007,
|
||||
R.drawable.image_00008,
|
||||
R.drawable.image_00009,
|
||||
R.drawable.image_00010,
|
||||
R.drawable.image_00011,
|
||||
R.drawable.image_00012,
|
||||
R.drawable.image_00013,
|
||||
R.drawable.image_00014,
|
||||
R.drawable.image_00015,
|
||||
R.drawable.image_00016,
|
||||
R.drawable.image_00017,
|
||||
R.drawable.image_00018,
|
||||
R.drawable.image_00019,
|
||||
R.drawable.image_00020,
|
||||
R.drawable.image_00021,
|
||||
R.drawable.image_00022,
|
||||
R.drawable.image_00023,
|
||||
R.drawable.image_00024,
|
||||
R.drawable.image_00025,
|
||||
R.drawable.image_00026,
|
||||
R.drawable.image_00027,
|
||||
R.drawable.image_00028,
|
||||
R.drawable.image_00029,
|
||||
R.drawable.image_00030,
|
||||
R.drawable.image_00031,
|
||||
R.drawable.image_00032,
|
||||
R.drawable.image_00033,
|
||||
R.drawable.image_00034,
|
||||
R.drawable.image_00035,
|
||||
R.drawable.image_00036,
|
||||
R.drawable.image_00037,
|
||||
R.drawable.image_00038,
|
||||
R.drawable.image_00039,
|
||||
R.drawable.image_00040,
|
||||
R.drawable.image_00041,
|
||||
R.drawable.image_00042,
|
||||
R.drawable.image_00043,
|
||||
R.drawable.image_00044,
|
||||
R.drawable.image_00045,
|
||||
R.drawable.image_00046,
|
||||
R.drawable.image_00047,
|
||||
R.drawable.image_00048,
|
||||
R.drawable.image_00049,
|
||||
R.drawable.image_00050,
|
||||
R.drawable.image_00051,
|
||||
R.drawable.image_00052,
|
||||
R.drawable.image_00053,
|
||||
R.drawable.image_00054,
|
||||
R.drawable.image_00055,
|
||||
R.drawable.image_00056,
|
||||
R.drawable.image_00057,
|
||||
R.drawable.image_00058,
|
||||
R.drawable.image_00059,
|
||||
R.drawable.image_00060,
|
||||
R.drawable.image_00061,
|
||||
R.drawable.image_00062,
|
||||
R.drawable.image_00063,
|
||||
R.drawable.image_00064,
|
||||
R.drawable.image_00065,
|
||||
R.drawable.image_00066,
|
||||
R.drawable.image_00067,
|
||||
R.drawable.image_00068,
|
||||
R.drawable.image_00069,
|
||||
R.drawable.image_00070,
|
||||
R.drawable.image_00071,
|
||||
R.drawable.image_00072,
|
||||
R.drawable.image_00073,
|
||||
R.drawable.image_00074
|
||||
};
|
||||
private Integer[] startingAnimIds = new Integer[]{
|
||||
R.drawable.light_00000,
|
||||
R.drawable.light_00001,
|
||||
R.drawable.light_00002,
|
||||
R.drawable.light_00003,
|
||||
R.drawable.light_00004,
|
||||
R.drawable.light_00005,
|
||||
R.drawable.light_00006,
|
||||
R.drawable.light_00007,
|
||||
R.drawable.light_00008,
|
||||
R.drawable.light_00009,
|
||||
R.drawable.light_00010,
|
||||
R.drawable.light_00011,
|
||||
R.drawable.light_00012,
|
||||
R.drawable.light_00013
|
||||
};
|
||||
|
||||
public TaxiPassengerStartAutopilotView(Context context) {
|
||||
super(context);
|
||||
@@ -38,8 +136,23 @@ public class TaxiPassengerStartAutopilotView extends RelativeLayout implements V
|
||||
private void initView(Context context) {
|
||||
view = LayoutInflater.from(context).inflate(R.layout.taxi_p_start_autopilot_view, this,true);
|
||||
mStartAutopilotBtn = view.findViewById(R.id.taxi_p_start_autopilot);
|
||||
mAutopilotStartingImage = view.findViewById(R.id.taxi_p_autopilot_starting);
|
||||
mStartAutopilotBtn.setOnClickListener(this);
|
||||
// mAutopilotStartingImage = view.findViewById(R.id.taxi_p_autopilot_starting);
|
||||
svCarStartingFrame = view.findViewById(R.id.taxi_p_autopilot_starting);
|
||||
svCarStartingFrame.setBitmapIds(Arrays.asList(startingAnimIds));
|
||||
svCarStartingFrame.setDuration(1680);
|
||||
|
||||
svBtnBgFrame = view.findViewById(R.id.taxi_p_start_autopilot_btn_sfv);
|
||||
svBtnBgFrame.setBitmapIds(Arrays.asList(btnBgAnimIds));
|
||||
svBtnBgFrame.setDuration(7500);
|
||||
|
||||
svCarStartingFrame.setOnLongClickListener(new OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
TaxiPassengerModel.getInstance().startServicePilotDone();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setOnClickStartAutopilotBtnCallback(ITPClickStartAutopilotCallback clickCallback){
|
||||
@@ -50,6 +163,10 @@ public class TaxiPassengerStartAutopilotView extends RelativeLayout implements V
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.taxi_p_start_autopilot){
|
||||
//开启动画和自动驾驶
|
||||
if (!(boolean)mStartAutopilotBtn.getTag()){
|
||||
ToastUtils.showLong(R.string.taxi_p_start_autopilot_un_click_tip);
|
||||
return;
|
||||
}
|
||||
if (!isStarting){
|
||||
startOrStopLoadingAnim(true);
|
||||
if (mClickCallback != null) mClickCallback.onClickCallback();
|
||||
@@ -58,22 +175,20 @@ public class TaxiPassengerStartAutopilotView extends RelativeLayout implements V
|
||||
}
|
||||
|
||||
public void updateStartAutopilotBtnStatus(boolean isClickable){
|
||||
|
||||
svCarStartingFrame.setBackgroundResource(R.drawable.light_00000);
|
||||
|
||||
if (mStartAutopilotBtn == null) return;
|
||||
mStartAutopilotBtn.setClickable(isClickable);
|
||||
|
||||
mStartAutopilotBtn.setTag(isClickable);
|
||||
mStartAutopilotBtn.setText(
|
||||
mContext.getResources().getString(R.string.taxi_p_start_autopilot_txt));
|
||||
if (isClickable){ //可点击状态下UI
|
||||
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mStartAutopilotBtn.getLayoutParams();
|
||||
params.bottomMargin = 0;
|
||||
mStartAutopilotBtn.setLayoutParams(params);
|
||||
|
||||
if (isClickable){ //高亮可点击状态下UI
|
||||
mStartAutopilotBtn.setTextColor(
|
||||
mContext.getResources().getColor(R.color.taxi_p_start_autopilot_txt_color));
|
||||
mStartAutopilotBtn.setBackground(mContext.getResources().getDrawable(R.drawable.anmi_flow));
|
||||
startAutopilotBgAnimatorDrawable(true);
|
||||
}else {// 不可点击状态下 UI
|
||||
LayoutParams params = (LayoutParams) mStartAutopilotBtn.getLayoutParams();
|
||||
params.bottomMargin = 294;
|
||||
mStartAutopilotBtn.setLayoutParams(params);
|
||||
}else {// 置灰色可点击状态下 UI
|
||||
mStartAutopilotBtn.setBackground(
|
||||
mContext.getResources().getDrawable(R.drawable.taxi_p_start_autopilot_txt_btn_bg));
|
||||
mStartAutopilotBtn.setTextColor(
|
||||
@@ -84,48 +199,75 @@ public class TaxiPassengerStartAutopilotView extends RelativeLayout implements V
|
||||
|
||||
public void startAutopilotBgAnimatorDrawable(boolean isStart){
|
||||
if (isStart){
|
||||
if (mAnimationBtnDrawable == null) {
|
||||
mAnimationBtnDrawable = (AnimationDrawable) mStartAutopilotBtn.getBackground();
|
||||
}
|
||||
if (mAnimationBtnDrawable.isRunning()) {
|
||||
return;
|
||||
}
|
||||
mAnimationBtnDrawable.selectDrawable(0);
|
||||
mAnimationBtnDrawable.start();
|
||||
svBtnBgFrame.setRepeatTimes(-1);
|
||||
svBtnBgFrame.setFrameFinishCallback(new FrameFinishCallback() {
|
||||
@Override
|
||||
public void onFinishCallback() {
|
||||
// svBtnBgFrame.setBackgroundResource(R.drawable.image_00000);
|
||||
}
|
||||
});
|
||||
svBtnBgFrame.start();
|
||||
}else {
|
||||
if (mAnimationBtnDrawable != null) {
|
||||
mAnimationBtnDrawable.stop();
|
||||
}
|
||||
mAnimationBtnDrawable = null;
|
||||
svBtnBgFrame.reset();
|
||||
svBtnBgFrame.setBackground(null);
|
||||
}
|
||||
// if (isStart){
|
||||
// if (mAnimationBtnDrawable == null) {
|
||||
// mAnimationBtnDrawable = (AnimationDrawable) mStartAutopilotBtn.getBackground();
|
||||
// }
|
||||
// if (mAnimationBtnDrawable.isRunning()) {
|
||||
// return;
|
||||
// }
|
||||
// mAnimationBtnDrawable.selectDrawable(0);
|
||||
// mAnimationBtnDrawable.start();
|
||||
// }else {
|
||||
// if (mAnimationBtnDrawable != null) {
|
||||
// mAnimationBtnDrawable.stop();
|
||||
// }
|
||||
// mAnimationBtnDrawable = null;
|
||||
// }
|
||||
}
|
||||
|
||||
private void startingAutopilotAnimatorDrawable(boolean isStart){
|
||||
private void startingCarBgAnimatorDrawable(boolean isStart){
|
||||
if (isStart){
|
||||
if (mAnimationStartingDrawable == null) {
|
||||
mAnimationStartingDrawable = (AnimationDrawable) mAutopilotStartingImage.getBackground();
|
||||
}
|
||||
if (mAnimationStartingDrawable.isRunning()) {
|
||||
return;
|
||||
}
|
||||
mAnimationStartingDrawable.selectDrawable(0);
|
||||
mAnimationStartingDrawable.start();
|
||||
svCarStartingFrame.setRepeatTimes(-1);
|
||||
svCarStartingFrame.setFrameFinishCallback(new FrameFinishCallback() {
|
||||
@Override
|
||||
public void onFinishCallback() {
|
||||
}
|
||||
});
|
||||
svCarStartingFrame.setBackground(null);
|
||||
svCarStartingFrame.start();
|
||||
}else {
|
||||
if (mAnimationStartingDrawable != null) {
|
||||
mAnimationStartingDrawable.selectDrawable(0);
|
||||
mAnimationStartingDrawable.stop();
|
||||
}
|
||||
mAnimationStartingDrawable = null;
|
||||
svCarStartingFrame.reset();
|
||||
svCarStartingFrame.setBackgroundResource(R.drawable.light_00000);
|
||||
}
|
||||
|
||||
// if (isStart){
|
||||
// if (mAnimationStartingDrawable == null) {
|
||||
// mAnimationStartingDrawable = (AnimationDrawable) mAutopilotStartingImage.getBackground();
|
||||
// }
|
||||
// if (mAnimationStartingDrawable.isRunning()) {
|
||||
// return;
|
||||
// }
|
||||
// mAnimationStartingDrawable.selectDrawable(0);
|
||||
// mAnimationStartingDrawable.start();
|
||||
// }else {
|
||||
// if (mAnimationStartingDrawable != null) {
|
||||
// mAnimationStartingDrawable.selectDrawable(0);
|
||||
// mAnimationStartingDrawable.stop();
|
||||
// }
|
||||
// mAnimationStartingDrawable = null;
|
||||
// }
|
||||
}
|
||||
|
||||
public void startOrStopLoadingAnim(boolean start) {
|
||||
startingAutopilotAnimatorDrawable(start);
|
||||
startingCarBgAnimatorDrawable(start);
|
||||
if (start) {
|
||||
isStarting = true;
|
||||
|
||||
mStartAutopilotBtn.setText(getResources().getString(R.string.taxi_p_start_autopilot_loading));
|
||||
mStartAutopilotBtn.setTextColor(getResources().getColor(R.color.taxi_p_start_autopilot_txt_un_color));
|
||||
mStartAutopilotBtn.setTextColor(getResources().getColor(R.color.taxi_p_start_autopilot_txt_color));
|
||||
|
||||
startingAutopilotCountDown();
|
||||
} else {
|
||||
@@ -147,6 +289,7 @@ public class TaxiPassengerStartAutopilotView extends RelativeLayout implements V
|
||||
@Override
|
||||
public void run() { //未启动成功10s后做处理
|
||||
if (isStarting){ //判断动画是否在进行
|
||||
ToastUtils.showLong(R.string.taxi_p_start_autopilot_fail_10s_tip);
|
||||
startOrStopLoadingAnim(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,24 @@
|
||||
android:layout_height="match_parent"
|
||||
tools:ignore="MissingDefaultResource"
|
||||
android:background="@drawable/taxi_p_passenger_start_panel_bg">
|
||||
<ImageView
|
||||
android:id="@+id/taxi_p_autopilot_starting"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="@drawable/starting_anmi_flow"
|
||||
|
||||
<com.mogo.och.common.module.wigets.sfv.FrameSurfaceView
|
||||
android:id="@+id/taxi_p_autopilot_starting"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<com.mogo.och.common.module.wigets.sfv.FrameSurfaceView
|
||||
android:id="@+id/taxi_p_start_autopilot_btn_sfv"
|
||||
android:layout_width="1000px"
|
||||
android:layout_height="500px"
|
||||
android:clickable="false"
|
||||
android:elevation="1dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
<TextView
|
||||
android:id="@+id/taxi_p_start_autopilot"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -25,8 +34,8 @@
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/taxi_p_start_autopilot_txt_un_color"
|
||||
android:elevation="5dp"
|
||||
android:clickable="false"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
app:layout_constraintLeft_toLeftOf="@+id/taxi_p_start_autopilot_btn_sfv"
|
||||
app:layout_constraintRight_toRightOf="@+id/taxi_p_start_autopilot_btn_sfv"
|
||||
app:layout_constraintTop_toTopOf="@+id/taxi_p_start_autopilot_btn_sfv"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/taxi_p_start_autopilot_btn_sfv"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -34,4 +34,6 @@
|
||||
|
||||
<string name="taxi_p_start_autopilot_txt">点击开始</string>
|
||||
<string name="taxi_p_start_autopilot_loading">启动中...</string>
|
||||
<string name="taxi_p_start_autopilot_fail_10s_tip">自动驾驶启动失败,请与司机确认车辆状态</string>
|
||||
<string name="taxi_p_start_autopilot_un_click_tip">车辆尚未完成准备,不能启动自动驾驶</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user