主頁 > 移動端開發 > Android高級UI之Canvas深度分析—變換技巧,狀態保存

Android高級UI之Canvas深度分析—變換技巧,狀態保存

2021-12-09 09:19:32 移動端開發

前言

在前面我們把Paint關于UI顏色樣式的處理進行了學習, 其實真正高級部分就是三個點,渲染,濾鏡,圖形組合,而我們圖形繪制比較重要的另一個物件Canvas也是需要我們去重點掌握的,那么這次咱們來進行Canvas的深層次的學習,主要了解有兩個點

  1. Canvas的變換使用技巧
  2. Canvas的狀態,Canvas Layer

1.Canvas基本概念

直面意思是畫布,其實是分裝的一個工具類(繪制會話,用來和底層溝通最終交給底層繪制),一個Canvas類物件有四大基本要素:

  • 一個是用來保存像素的bitmap
  • 一個Canvas在Bitmap上進行繪制操作
  • 繪制的東西
  • 繪制的畫筆Paint

2.Canvas變換操作----坐標系概念

在我們進行canvas操作的時候我們會有一個問題產生,在進行圖形的平移,旋轉操作時,我們沒有去更改原始的坐標,只通過了非常簡單的幾個api就直接進行了移動,那么中間他的具體到底是發生了什么,通過之前在繪制流程當中draw時我們發現在下面我已經縮減了之后的代碼上我門發現, 在繪制之初就產生了一個矩形,并且他通過面板進行了一次初始化

 private void draw(boolean fullRedrawNeeded) {
    Surface surface = mSurface;
   ...

    final Rect dirty = mDirty;
    if (mSurfaceHolder != null) {
        // The app owns the surface, we won't draw.
        dirty.setEmpty();
        if (animating && mScroller != null) {
            mScroller.abortAnimation();
        }
        return;
    }

    if (fullRedrawNeeded) {
        mAttachInfo.mIgnoreDirtyState = true;
        dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    }

    int xOffset = -mCanvasOffsetX;
    int yOffset = -mCanvasOffsetY + curScrollY;
    final WindowManager.LayoutParams params = mWindowAttributes;
    final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
    if (surfaceInsets != null) {
        xOffset -= surfaceInsets.left;
        yOffset -= surfaceInsets.top;

        // Offset dirty rect for surface insets.
        dirty.offset(surfaceInsets.left, surfaceInsets.right);
    }
  ......

            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        }
    }

    if (animating) {
        mFullRedrawNeeded = true;
        scheduleTraversals();
    }
}

那么在上面的代碼當中我門可以看到在繪制開始之初, 在底層就確定了一個繪制區域,確定了canvas繪制位置的坐標,那么這個就是被稱之為我門canvas的坐標系,確定我們canvas繪制圖形的位置,那么,當我們進行了

    canvas.translate(50, 50);
    canvas.rotate(45);
    canvas.scale(1.5f, 0.5f);
    canvas.skew(1.73f, 0);

等操作的時候,我們的圖形繪制會直接發生改變,那么這個時候我門考慮一個問題,下圖中綠色的點移動到紅色的點,我們剛才所設定的canvas移動了嗎

image.png

其實很多通過會在這里認為我們canvas的坐標進行了移動,其實不然,在Canvas里面牽扯兩種坐標系:Canvas自己的坐標系、繪圖坐標系

2.1 Canvas的坐標系

它就在View的左上角,做坐標原點往右是X軸正半軸,往下是Y軸的正半軸,有且只有一個,唯一不變這一個其實就是在我們canvas當中在繪制之初由surface所初始化的那個點

2.2 繪圖坐標系

它不是唯一不變的,它與Canvas的Matrix有關系,當Matrix發生改變的時候,繪圖坐標系對應的進行改變,他有一個特性就是在這個程序中是不可逆的

那么其實實際就是我們在畫圖的時候,有一塊總面板,總面板不動, 而當我在開始進行繪制圖形的時候,有一個時時刻刻在動的面板,而這個面板就是具體去繪制我們圖形的畫板

image.png

那么里層的繪圖坐標系他的實際是用一個Matrix矩陣表示的,這個和我門之前的濾鏡矩陣表示差不多,只不過,繪圖坐標系的矩陣是一個2x2的矩陣傳入的值是由我們的canvas進行決議之后將自己想要的資料給底層底層自己計算所得

public void drawRect(@NonNull Rect r, @NonNull Paint paint) {
    throwIfHasHwBitmapInSwMode(paint);
    drawRect(r.left, r.top, r.right, r.bottom, paint);
}

那么在這里我們可以看到在進入底層native方法之前,實作會根據每一種繪制的不同對底層的資料進行傳入, 然后會計算出我門的繪制坐標系(此處底層不看,涉及c,我們這里明白這一點就行)

我們通過簡單設定translate、rotate、scale、skew來改變我們繪制圖形的位置時他的計算時依賴與另外一個矩陣來對繪圖坐標系進行改變,這是一個3x3的矩陣,它里面的九個引數

cosX -sinX translateX
sinX cosX translateY
0 0 scale

其中,sinX和cosX,代表的是旋轉角度的sin和cos值,注意旋轉的正方向是順時針方向,translateX和translateY代表的是平移的X和Y,scale代表的是縮放的大小,

我們可以通過getMatrix()的到這個矩陣,而通過看到底層原始碼,這里我能清晰的看到我們是直接呼叫底層的矩陣

@Deprecated
public void getMatrix(@NonNull Matrix ctm) {
    nGetMatrix(mNativeCanvasWrapper, ctm.native_instance);
}

那么這里我做了一組測驗

 RectF r = new RectF(0, 0, 400, 500);
    paint.setColor(Color.GREEN);
    canvas.drawRect(r, paint);
    float[] fs = new float[10];
            canvas.getMatrix().getValues(fs);
    for (int i = 0;i < fs.length;i++){
        Log.i("barry","fs:"+fs[i]);
    }
    //平移
    canvas.translate(50, 50);
    float[] fs2 = new float[10];
    canvas.getMatrix().getValues(fs2);
    for (int i = 0;i < fs2.length;i++){
        Log.i("barry","fs2:"+fs2[i]);
    }

    paint.setColor(Color.BLUE);
    canvas.drawRect(r, paint);

image.png

可以很明顯看到,矩陣進行平移之后這個矩陣資訊的變化,那么注意,繪圖矩陣的坐標系移動是一個不可逆轉的狀態也就是說,一旦矩陣移動完成之后,那么他不能回到之前的位置,具體效果如下

image.png

但是在我們的Canvas當中提供了save和restore方法來保存和還原變化操作,

    RectF r = new RectF(0, 0, 400, 500);
    paint.setColor(Color.GREEN);
    //畫完之后,繪圖坐標系定位在此處
    canvas.drawRect(r, paint);
    //save保存當前坐標
    canvas.save();

    //平移之后,坐標系發生改變
    canvas.translate(50, 50);
    
    paint.setColor(Color.BLUE);
    canvas.drawRect(r, paint);
    //通過restore進行還原到save保存時的坐標系
    canvas.restore();

image.png

但是想要知道這兩個方法是怎么進行操作的才能讓我們更加深入的去熟悉Canvas的使用技巧,那么我門必須去了解Canvas的狀態堆疊、Layer堆疊

3. Canvas的狀態保存—狀態堆疊、Layer堆疊

3.1 狀態堆疊

在前面我們提到坐標系的轉換是一個不可逆轉的,而我們可以通過save來進行保存restore進行恢復,其實我們在進行save操作時在canvas當中會將我門save下來的坐標系進行保存到一個堆疊當中,并且可以通過restore或者是restoreToCount進行操作下面通過一段測驗代碼我門印證下

public class MyView extends View {
private static final String TAG = "BARRY";

private Paint mPaint = null;
private Bitmap mBitmap = null;

public MyView(Context context) {
    this(context, null);
}

public MyView(Context context, AttributeSet attrs) {
    super(context, attires
    mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lsj);
    init();
}

public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

private void init() {
    mPaint = new Paint();
    mPaint.setColor(Color.RED);
    mPaint.setAntiAlias(true);
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setStrokeWidth(10);
}

@Override
protected void onDraw(Canvas canvas) {
    //第1次保存,并通過canvas.getSaveCount的到當前狀態堆疊容量
    canvas.save();
    Log.i(TAG, "Current SaveCount = " + canvas.getSaveCount());

    canvas.translate(400, 400);
    RectF rectF = new RectF(0,0,600,600);
        canvas.drawBitmap(mBitmap, null, rectF, mPaint);
    //第2次保存,并通過canvas.getSaveCount的到當前狀態堆疊容量
    canvas.save();
    Log.i(TAG, "Current SaveCount = " + canvas.getSaveCount());

    canvas.rotate(45);

    canvas.drawBitmap(mBitmap, null, rectF, mPaint);
    //第3次保存,并通過canvas.getSaveCount的到當前狀態堆疊容量
    canvas.save();
    Log.i(TAG, "Current SaveCount = " + canvas.getSaveCount());

    canvas.rotate(45);

    canvas.drawBitmap(mBitmap, null, rectF, mPaint);
    //第4次保存,并通過canvas.getSaveCount的到當前狀態堆疊容量
    canvas.save();
    Log.i(TAG, "Current SaveCount = " + canvas.getSaveCount());
    //通過canvas.restoreToCount出堆疊到第三層狀態
    canvas.restoreToCount(3);
    Log.i(TAG, "restoreToCount--Current SaveCount = " + canvas.getSaveCount());

    canvas.translate(0, 200);

    //rectF = new RectF(0,0,600,600);
    canvas.drawBitmap(mBitmap, null, rectF, mPaint);
    //通過canvas.restoreToCount出堆疊到第1層(最原始的那一層)狀態
    canvas.restoreToCount(1);
    Log.i(TAG, "restoreToCount--Current SaveCount = " + canvas.getSaveCount());
    canvas.drawBitmap(mBitmap, null, rectF, mPaint);
   }
 }   

image.png

image.png

image.png

那么其實我們這樣可以直接明白, 每一次的save其實實際上是用了一個堆疊保存了我的繪圖坐標系,這個堆疊被我們稱之為狀態堆疊起來, 而我們的restore就是一個出堆疊的程序,save、 restore方法來保存和還原變換操作Matrix以及Clip剪裁

3.2 Layer堆疊

在我們的canvas當中,提供了一個saveLayer的api主要做用是用來新建一個圖層,后續的繪圖操作都在新建的layer上面進行,當我們呼叫restore 或者 restoreToCount 時 更新到對應的圖層和畫布上

image.png

下面通過這段測驗代碼的效果我們來驗證當前的結論

public class MyView extends View {

Paint mPaint;
float mItemSize = 0;
float mItemHorizontalOffset = 0;
float mItemVerticalOffset = 0;
float mCircleRadius = 0;
float mRectSize = 0;
int mCircleColor = 0xffffcc44;//黃色
int mRectColor = 0xff66aaff;//藍色
float mTextSize = 25;

private static final Xfermode[] sModes = {
        new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
        new PorterDuffXfermode(PorterDuff.Mode.SRC),
        new PorterDuffXfermode(PorterDuff.Mode.DST),
        new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),
        new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
        new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
        new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
        new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),
        new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
        new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
        new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
        new PorterDuffXfermode(PorterDuff.Mode.XOR),
        new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
        new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
        new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
        new PorterDuffXfermode(PorterDuff.Mode.SCREEN)
};

private static final String[] sLabels = {
        "Clear", "Src", "Dst", "SrcOver",
        "DstOver", "SrcIn", "DstIn", "SrcOut",
        "DstOut", "SrcATop", "DstATop", "Xor",
        "Darken", "Lighten", "Multiply", "Screen"
};

public MyView(Context context) {
    super(context);
    init(null, 0);
}

public MyView(Context context, AttributeSet attrs) {
    super(context, attires
    init(attrs, 0);
}

public MyView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(attrs, defStyle);
}

private void init(AttributeSet attrs, int defStyle) {
    if(Build.VERSION.SDK_INT >= 11){
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setTextSize(mTextSize);
    mPaint.setTextAlign(Paint.Align.CENTER);
    mPaint.setStrokeWidth(2);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //設定背景色
    canvas.drawARGB(255, 139, 197, 186);

    int canvasWidth = canvas.getWidth();
    int canvasHeight = canvas.getHeight();

    for(int row = 0; row < 4; row++){
        for(int column = 0; column < 4; column++){
            canvas.save();
            //此處是建立新的圖層
            int layer = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
            mPaint.setXfermode(null);
            int index = row * 4 + column;
            float translateX = (mItemSize + mItemHorizontalOffset) * column;
            float translateY = (mItemSize + mItemVerticalOffset) * row;
            canvas.translate(translateX, translateY);
            //畫文字
            String text = sLabels[index];
            mPaint.setColor(Color.BLACK);
            float textXOffset = mItemSize / 2;
            float textYOffset = mTextSize + (mItemVerticalOffset - mTextSize) / 2;
            canvas.drawText(text, textXOffset, textYOffset, mPaint);
            canvas.translate(0, mItemVerticalOffset);
            //畫邊框
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(0xff000000);
            canvas.drawRect(2, 2, mItemSize - 2, mItemSize - 2, mPaint);
            mPaint.setStyle(Paint.Style.FILL);
            //畫圓
            mPaint.setColor(mCircleColor);
            float left = mCircleRadius + 3;
            float top = mCircleRadius + 3;
            canvas.drawCircle(left, top, mCircleRadius, mPaint);
            mPaint.setXfermode(sModes[index]);
            //畫矩形
            mPaint.setColor(mRectColor);
            float rectRight = mCircleRadius + mRectSize;
            float rectBottom = mCircleRadius + mRectSize;
            canvas.drawRect(left, top, rectRight, rectBottom, mPaint);
            mPaint.setXfermode(null);
            //canvas.restore();
            canvas.restoreToCount(layer);
        }
    }
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, old);)
    mItemSize = w / 4.5f;
    mItemHorizontalOffset = mItemSize / 6;
    mItemVerticalOffset = mItemSize * 0.426f;
    mCircleRadius = mItemSize / 3;
    mRectSize = mItemSize * 0.6f;
}
}

沒有加saveLayer的效果

image.png

加了saveLayer的效果

image.png

這段代碼我們可以看到,其實實際上就是我們之前的文章當中,xfermode的演示代碼,而在這段代碼當中我才用了saveLayer進行操作,通過上面兩個結果,一個是我加了saveLayer的,一個是沒加的, 那么從中我門可以明顯看到在沒有加的時候,xfermode的像素輸出效果直接將外層背景色也給清空了,而加入之后沒有,那么其實我們可以很明顯的明白如果用了layer那么其實實際上我們是在當前這個canvas圖形上面新建了一個圖層當我們呼叫restore 或者 restoreToCount 時 我們的繪制會更新到當前圖層

那么這個時候我們來詳細分析saveLayer的引數

canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
/**
 * Helper version of saveLayer() that takes 4 values rather than a RectF.
 *
 * @deprecated Use {@link #saveLayer(float, float, float, float, Paint)} instead.
 */
public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint,
        @Saveflags int saveFlags) {
    return nSaveLayer(mNativeCanvasWrapper, left, top, right, bottom,
            paint != null ? paint.getNativeInstance() : 0,
            saveFlags);
}

通過上訴方法的注釋,以及代碼我們明顯知道,前面四個引數,為上下左右四個點構成一個圖層區,Paint畫筆也可以繼承過來,而最后一個引數表示的是我們當前的保存形式,總共下面6種,這六個模式其實實際上講的就是告訴canvas當前保存那些資訊

  • MATRIX_SAVE_FLAG:只保存圖層的matrix矩陣 save,saveLayer
  • CLIP_SAVE_FLAG:只保存大小資訊 save,saveLayer
  • HAS_ALPHA_LAYER_SAVE_FLAG:表明該圖層有透明度,和下面的標識沖突,都設定時以下面的標志為準
  • FULL_COLOR_LAYER_SAVE_FLAG:完全保留該圖層顏色(和上一圖層合并時,清空上一圖層的重疊區域,保留該圖層的顏色)
  • CLIP_TO_LAYER_SAVE_:創建圖層時,會把canvas(所有圖層)裁剪到引數指定的范圍,如果省略這個flag將導致圖層開銷巨大(實際上圖層沒有裁剪,與原圖層一樣大)
  • ALL_SAVE_FLAG:保存所有資訊 save,saveLayer

從原始碼當中我發現其他幾種模式在高版本當中已經剔除,只保留了一種,就是我門的all_save_flag

  /** @hide */
@IntDef(flag = true,
        value = {
            ALL_SAVE_FLAG
        })
@Retention(RetentionPolicy.SOURCE)
public @interface Saveflags {}

那么這個時候我們來測驗一下

 public class MyView3 extends View {

public MyView3(Context context) {
    super(context);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    RectF rectF = new RectF(0,0,400,500);
    Paint paint = new Paint();
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(10);
    paint.setColor(Color.GREEN);

    canvas.drawRect(rectF, paint);
    canvas.translate(50,50);

    canvas.saveLayer(0,0,canvas.getWidth(),canvas.getHeight(),null,Canvas.ALL_SAVE_FLAG);
    //canvas.save();
    canvas.drawColor(Color.BLUE);// 通過drawColor可以發現saveLayer是新建了一個圖層,
                                // 同時結合Lsn5的16種Xfermode疊加形式Demo可以驗證是新建的透明圖層
    paint.setColor(Color.YELLOW);
    canvas.drawRect(rectF,paint);
    //canvas.restore();
    canvas.restore();

    RectF rectF1 = new RectF(10,10,300,400);
    paint.setColor(Color.RED);
    canvas.drawRect(rectF1,paint);

}

加了saveLayer

image.png

沒加saveLayer

image.png

那么這段代碼也驗證了我們上訴的理論,在加了saveLayer之后,背景色被繪制到了另外一個圖層導致前面有一節空白的,同時也得出了一個有趣的結論,貌似,平移操作也被繼承了,其實這里我們的出一個結論saveLayer會將之前的一些Canvas狀態操作延續過來,這里是通過之前的最后一個引數設定成ALL_SAVE_FLAG完成,他在新建圖層的時候完成了保留當前所有資訊狀態的操作.

總結

Canvas里面牽扯兩種坐標系: Canvas自己的坐標系、繪圖坐標系

  • Canvas的坐標系:它就在View的左上角,做坐標原點往右是X軸正半軸,往下是Y軸的正半軸,有且只有一個,唯一不變
  • 繪圖坐標系:它不是唯一不變的,它與Canvas的Matrix有關系,當Matrix發生改變的時候,繪圖坐標系對應的進行改變,同時這個程序是不可逆的(save和restore方法來保存和還原變化操作),Matrix又是通過我們設定translate、rotate、scale、skew來進行改變的

Canvas的狀態保存—狀態堆疊、Layer堆疊

  • 狀態堆疊– save、 restore方法來保存和還原變換操作Matrix以及Clip剪裁,也可以通過restoretoCount直接還原到對應堆疊的保存狀態
  • Layer堆疊— saveLayer的時候都會新建一個透明的圖層(離屏Bitmap-離屏緩沖),并且會將saveLayer之前的一些Canvas操作延續過來,后續的繪圖操作都在新建的layer上面進行,當我們呼叫restore 或者 restoreToCount 時 更新到對應的圖層和畫布上

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/377138.html

標籤:其他

上一篇:記事本(一)

下一篇:音視頻技術是否成為Android新主流?6年音視頻技術專家帶你解密……

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more