主頁 > 移動端開發 > 【踩坑記錄】Android自定義控制元件——流程進度條

【踩坑記錄】Android自定義控制元件——流程進度條

2020-12-17 12:40:21 移動端開發

前言

實習期間被分配到的第一個任務,完成大概如圖這樣一個界面,乍一看,整個界面的布局還是十分清晰的,即使是新手也能輕易完成,唯一的難題應該就是這個紅色的進度條了,我一開始考慮使用TextView的drawableLeft來實作,但又感覺不如自定義控制元件來得靈活,遂決定使用自定義控制元件的方式實作,然而,我高估了自己的水平😅,程序中遇到了不少坑,花了幾天才誤打誤撞地完成這個“簡易”進度條,也正因如此,才有了這篇文章來記錄一下思考程序、遇到的問題以及解決方案,

設計圖

設計分析

實作這樣一個控制元件,要考慮的方面有:繪制(Draw)、測量(Measure)以及屬性(Attribute),

  • 繪制,顯而易見,繪制一條直線和幾個圓形即可,
  • 測量,我選擇以圓形的直徑為控制元件的寬(width),父容器的高度為控制元件的高(height),
  • 屬性,比較自由,如直線的粗細、圓形的半徑、圓環的位置等,為簡便起見,只考慮幾個比較關鍵的屬性,

實作

根據上述分析,代碼的邏輯也基本理清了,實作起來應該是水到渠成,

創建類

新建MyProgressView類,繼承自View類,并實作相關構造方法,使代碼入口一致,

public class MyProgressView extends View {

    private static final String TAG = "MyProgressView";

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

    public MyProgressView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

設定屬性

為方便使用,部分屬性希望能在布局檔案中直接修改,將其添加至attrs.xml檔案中,并在控制元件的構造方法中完成初始化,另一部分屬性,可能不方便于布局檔案中修改或其他原因,為其設定get/set方法,

    <declare-styleable name="MyProgressView">
        <attr name="circleRadius" format="dimension"/>
        <attr name="lineWidth" format="dimension"/>
        <attr name="circlePosition1" format="float"/>
        <attr name="circlePosition2" format="float"/>
        <attr name="circlePosition3" format="float"/>
    </declare-styleable>
public class MyProgressView extends View {

    private static final String TAG = "MyProgressView";
	
	//控制元件的高度
    private int mHeight;
	
    //繪制的起始點
    private float mStartY = DEFAULT_START_Y;
    public static final float DEFAULT_START_Y = 0;

    //實線的寬度
    private float mLineWidth;

    //圓環的半徑
    private float mRadius;

    //圓環的位置(圓心)
    private float[] mCirclePositions = new float[3];

    //當前行程
    private int mProgress = -1;

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

    public MyProgressView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //初始化屬性
        initAttrs(context, attrs);
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyProgressView);
        //圓環的半徑
        int defaultRadius = SizeUtils.dip2px(5);
        mRadius = a.getDimension(R.styleable.MyProgressView_circleRadius, defaultRadius);
        //實線的寬度
        int defaultWidth = SizeUtils.dip2px(2);
        mLineWidth = a.getDimension(R.styleable.MyProgressView_lineWidth, defaultWidth);
        /*
         * 三個圓環的位置(y軸)
         * 推薦使用setCirclePositions方法設定
         */
        mCirclePositions[0] = a.getFloat(R.styleable.MyProgressView_circlePosition1, -1);
        mCirclePositions[1] = a.getFloat(R.styleable.MyProgressView_circlePosition2, -1);
        mCirclePositions[2] = a.getFloat(R.styleable.MyProgressView_circlePosition3, -1);
        //回收
        a.recycle();
    }

	/*
     * 省略部分屬性的get/set方法
     */
}

繪制

繪制直線和圓形所需要的的相關屬性已經定義好了,除此之外還需要實體化對應的畫筆,

    private void initPaints() {
        //初始化實線畫筆
        mLinePaint = new Paint();
        mLinePaint.setColor(Color.parseColor("#FF0000"));
        mLinePaint.setStrokeWidth(mLineWidth);
        //初始化圓形畫筆
        innerCirclePaint = new Paint();
        outerCirclePaint = new Paint();
        innerCirclePaint.setColor(Color.parseColor("#FFFFFF"));
        outerCirclePaint.setColor(Color.parseColor("#D0021B"));
    }

緊接著就是繪制程序,圓環利用兩個重合的大小圓形實作,根據mProgress的值決定是否只繪制一個圓形,以表示當前流程,

	@Override
    protected void onDraw(Canvas canvas) {
        Log.i(TAG,"-- onDraw --");
        super.onDraw(canvas);
        //繪制實線
        canvas.drawLine(mRadius, mStartY, mRadius, mHeight, mLinePaint);
        //繪制圓環
        if (mCirclePositions != null && mCirclePositions.length > 0) {
            for (int i = 1; i <= 3; i ++) {
                float f = mCirclePositions[i - 1];
                if (f == -1) continue;  //未設值,不繪制
                canvas.drawCircle(mRadius, f, mRadius, outerCirclePaint);
                if (i != mProgress) {
                    canvas.drawCircle(mRadius, f, mRadius / 2, innerCirclePaint);
                }
            }
        }
    }

測量

根據之前的分析,測量程序十分簡單,控制元件的寬(width)和高(height)都寫死,分別為圓形的直徑和父容器的高度,

	@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        Log.i(TAG, "heightSize --> " + heightSize);
        Log.i(TAG, "widthSize --> "+ widthSize);
        Log.i(TAG, "heightMode --> " + heightMode);
        Log.i(TAG, "widthMode --> " + widthMode);
        Log.i(TAG, "----------------------");
        mHeight = heightSize;
        setMeasuredDimension((int) mRadius * 2, heightSize);
    }

使用測驗

至此,我認為自定義進度條已經完成,迫不及待地將其加入布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
	android:id="@+id/container"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    tools:context=".MainActivity"
    xmlns:app="http://schemas.android.com/apk/res-auto">
	
	<!--layout_width可以為任意值,不會影響結果-->
    <com.mone.customview.progressView.MyProgressView
        android:id="@+id/visit_progress_view"
        android:layout_width="10dp"
        android:layout_height="wrap_content"
        app:circleRadius="6dp"
        android:layout_alignParentStart="true" />

    <TextView
        android:id="@+id/title1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="@style/text_title_style"
        android:textColor="#000000"
        android:text="流程1"/>

    <TextView
        android:id="@+id/content1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/title1"
        style="@style/text_content_style"
        android:textColor="#aaaaaa"
        android:text="內容內容很多內容內容內容很多內容內容內容很多內容內容內容很多內容內容內容很多內容很多內容很多內容很多內容很多內容"/>

    <TextView
        android:id="@+id/title2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/content1"
        style="@style/text_title_style"
        android:textColor="#000000"
        android:text="流程2"/>

    <TextView
        android:id="@+id/content2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/title2"
        style="@style/text_content_style"
        android:textColor="#aaaaaa"
        android:text="內容內容很多內容內容內容很多內容內容內容很多內容內容內容很多內容內容內容很多內容很多內容很多內容很多內容很多內容很多內容很多內容很多內容"/>

    <TextView
        android:id="@+id/title3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/content2"
        style="@style/text_title_style"
        android:textColor="#000000"
        android:text="流程3"/>

    <TextView
        android:id="@+id/content3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/title3"
        style="@style/text_content_style"
        android:textColor="#aaaaaa"
        android:text="內容內容很多內容內容內容很多內容內容內容很多內容內容內容很多內容內容內容很多內容很多內容很多內容很多內容很多內容很多內容"/>

</RelativeLayout>

并設定好相應的屬性:

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化組件
        container = findViewById(R.id.container);
        myProgressView = findViewById(R.id.visit_progress_view);
        title1 = findViewById(R.id.title1);
        title2 = findViewById(R.id.title2);
        title3 = findViewById(R.id.title3);
        //設定進度條
        container.post(new Runnable() {
            @Override
            public void run() {
                int padding = container.getPaddingTop();
                float y1 = (title1.getTop() + title1.getBottom() - 2 * padding) >> 1;
                float y2 = (title2.getTop() + title2.getBottom() - 2 * padding) >> 1;
                float y3 = (title3.getTop() + title3.getBottom() - 2 * padding) >> 1;
                myProgressView.setStartY(y1);
                myProgressView.setCirclePositions(new float[]{y1, y2, y3});
                myProgressView.invalidate();
            }
        });
        myProgressView.setProgress(2);
    }

啟動測驗,查看結果:
測驗結果1
出現這樣的結果是正常的,但這不符合需求,我希望進度條的高度根據右側的內容變化,而不是像這樣占滿了螢屏,這也是我實作這個自定義控制元件程序中最頭疼的一件事,為此,我特意去學習研究了onMeasure方法,也就是我的上一篇文章:自定義控制元件之onMeasure方法的研究整理,最終解決了這個問題,根源就在于處理不同情況下的測量程序,

EXACTLYAT_MOSTUNSPECIFIED
dp/pxEXACTLY
childSize
EXACTLY
childSize
EXACTLY
childSize
match_parentEXACTLY
parentSize
AT_MOST
parentSize
UNSPECIFIED
0
wrap_contentAT_MOST
parentSize
AT_MOST
parentSize
UNSPECIFIED
0

依舊搬出這張表,橫軸對應父容器的測量模式,縱軸對應子View的layoutParams,值為onMeasure方法中的對應引數,
剛才的布局中,RelativeLayout的layout_height屬性是wrap_content,對應AT_MOST模式,myProgressView的layout_height屬性是wrap_content,那么,從引數heightMeasureSpec中取出的值和模式就分別是parentSize和AT_MOST,也就出現了那樣的結果,

	@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	//省略部分代碼
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        setMeasuredDimension((int) mRadius * 2, heightSize);
    }

解決問題

既然如此,那么要解決這個問題的思路就很明確了,當回傳的模式為AT_MOST時,做不一樣的處理即可,我的解決方案如下:
首先修改onMeasure方法,當測驗模式為AT_MOST時,讓控制元件的測量高為0,

	@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        Log.i(TAG, "heightSize --> " + heightSize);
        Log.i(TAG, "widthSize --> "+ widthSize);
        Log.i(TAG, "heightMode --> " + heightMode);
        Log.i(TAG, "widthMode --> " + widthMode);
        Log.i(TAG, "----------------------");
        mHeight = heightSize;
        //處理不同模式下的測量值
        if (heightMode == MeasureSpec.EXACTLY) {
            setMeasuredDimension((int) mRadius * 2, heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension((int) mRadius * 2, 0);
        }
    }

然后調整初始化進度條的方法,在那里才為進度條設定高度值(此時父容器已經測量完成,由于進度條的測量高度為0,父容器高度只根據TextView的測量高度和padding值決定),

    /**
     * 初始化進度條組件
     */
    private void setupProgressView() {
        container = findViewById(R.id.container);
        container.post(new Runnable() {
            @Override
            public void run() {
                int height = container.getMeasuredHeight();
                Log.i(TAG, "容器的最終高度 --> " + height);
                ViewGroup.LayoutParams layoutParams = myProgressView.getLayoutParams();
                layoutParams.height = height;
                myProgressView.setLayoutParams(layoutParams);
                myProgressView.setHeight(height);
                //設定進度條圓點位置
                int padding = container.getPaddingTop();
                float y1 = (title1.getTop() + title1.getBottom() - 2 * padding) >> 1;
                float y2 = (title2.getTop() + title2.getBottom() - 2 * padding) >> 1;
                float y3 = (title3.getTop() + title3.getBottom() - 2 * padding) >> 1;
                myProgressView.setStartY(y1);
                myProgressView.setCirclePositions(new float[]{y1, y2, y3});
            }
        });
        //設定進度
        myProgressView.setProgress(2);
    }

這樣修改以后,最終的結果也符合需求:
測驗圖2

第三種模式

上一輪修改onMeasure方法時,只考慮了EXACTLY和AT_MOST模式,卻忽略了第三種模式:UNSPECIFIED模式,雖然大部分文章告訴我,這種模式很少用到,一般無需考慮,但我直接遇到了這種情況:當需要用滑動布局嵌套時,就需要考慮這種模式,

<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="10dp">

        <com.mone.customview.progressView.MyProgressView
            android:id="@+id/visit_progress_view"
            android:layout_width="10dp"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true" />

		<!-- 省略部分代碼 -->
		
	</RelativeLayout>

</androidx.core.widget.NestedScrollView>

可以看一下輸出的Log,其中heightMode = 0,即表示UNSPECIFIED模式,
在這里插入圖片描述
不過解決的方式也很簡單,與AT_MOST模式類似,在測量程序中設定測量值為0(回傳值就是0,說明滑動布局有意這樣設計),在初始化的程序中再設定高度即可,

		//處理不同模式下的測量值
        if (heightMode == MeasureSpec.EXACTLY) {
            setMeasuredDimension((int) mRadius * 2, heightSize);
        } else {
            setMeasuredDimension((int) mRadius * 2, 0);
        }

完整代碼

流程進度條

/**
 * 進度指示器
 */
public class MyProgressView extends View {

    private static final String TAG = "MyProgressView";

    //控制元件的高度
    private int mHeight;

    //繪制的起始點
    private float mStartY = DEFAULT_START_Y;
    public static final float DEFAULT_START_Y = 0;

    //實線的寬度
    private float mLineWidth;

    //圓環的半徑
    private float mRadius;

    //圓環的位置(圓心)
    private float[] mCirclePositions = new float[3];

    //當前流程
    private int mProgress = -1;

    //畫筆
    private Paint mLinePaint;
    private Paint innerCirclePaint;
    private Paint outerCirclePaint;

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

    public MyProgressView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //初始化屬性
        initAttrs(context, attrs);
        //初始化畫筆
        initPaints();
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyProgressView);
        //圓環的半徑
        int defaultRadius = SizeUtils.dip2px(5);
        mRadius = a.getDimension(R.styleable.MyProgressView_circleRadius, defaultRadius);
        //實線的寬度
        int defaultWidth = SizeUtils.dip2px(2);
        mLineWidth = a.getDimension(R.styleable.MyProgressView_lineWidth, defaultWidth);
        /*
         * 三個圓環的位置(y軸)
         * 推薦使用setCirclePositions方法設定
         */
        mCirclePositions[0] = a.getFloat(R.styleable.MyProgressView_circlePosition1, -1);
        mCirclePositions[1] = a.getFloat(R.styleable.MyProgressView_circlePosition2, -1);
        mCirclePositions[2] = a.getFloat(R.styleable.MyProgressView_circlePosition3, -1);
        //回收
        a.recycle();
    }


    /**
     * 初始化畫筆
     */
    private void initPaints() {
        //初始化實線畫筆
        mLinePaint = new Paint();
        mLinePaint.setColor(Color.parseColor("#FF0000"));
        mLinePaint.setStrokeWidth(mLineWidth);
        //初始化圓形畫筆
        innerCirclePaint = new Paint();
        outerCirclePaint = new Paint();
        innerCirclePaint.setColor(Color.parseColor("#FFFFFF"));
        outerCirclePaint.setColor(Color.parseColor("#D0021B"));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        Log.i(TAG, "heightSize --> " + heightSize);
        Log.i(TAG, "widthSize --> "+ widthSize);
        Log.i(TAG, "heightMode --> " + heightMode);
        Log.i(TAG, "widthMode --> " + widthMode);
        Log.i(TAG, "----------------------");
        mHeight = heightSize;
        if (heightMode == MeasureSpec.EXACTLY) {
            setMeasuredDimension((int) mRadius * 2, heightSize);
        } else {
            setMeasuredDimension((int) mRadius * 2, 0);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Log.i(TAG,"-- onDraw --");
        super.onDraw(canvas);
        //繪制實線
        canvas.drawLine(mRadius, mStartY, mRadius, mHeight, mLinePaint);
        //繪制圓環
        if (mCirclePositions != null && mCirclePositions.length > 0) {
            for (int i = 1; i <= 3; i ++) {
                float f = mCirclePositions[i - 1];
                if (f == -1) continue;  //未設值,不繪制
                canvas.drawCircle(mRadius, f, mRadius, outerCirclePaint);
                if (i != mProgress) {
                    canvas.drawCircle(mRadius, f, mRadius / 2, innerCirclePaint);
                }
            }
        }
    }

    /**
     * 設定當前進度
     * @param num
     */
    public void setProgress(int num) {
        if (num < 1 || num > 3) return;
        mProgress = num;
        invalidate();
    }

    /**
     * 設定View的高度
     * @param height
     */
    public void setHeight(int height) {
        mHeight = height;
    }

    /**
     * 設定繪制的Y軸起始點
     * @param y
     */
    public void setStartY(float y) {
        mStartY = y;
    }

    /**
     * 設定圓環的半徑
     * @param radius
     */
    public void setRadius(float radius) {
        mRadius = radius;
    }

    public float getRadius() {
        return mRadius;
    }

    /**
     * 設定實線的寬度
     * @param width
     */
    public void setLineWidth(float width) {
        mLineWidth = width;
        mLinePaint.setStrokeWidth(mLineWidth);
    }

    /**
     * 設定圓環的位置
     * @param floats
     */
    public void setCirclePositions(float[] floats) {
        this.mCirclePositions = floats;
    }
}

使用方式

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private RelativeLayout container;
    private MyProgressView myProgressView;
    private TextView title1, title2, title3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化組件
        container = findViewById(R.id.container);
        myProgressView = findViewById(R.id.visit_progress_view);
        title1 = findViewById(R.id.title1);
        title2 = findViewById(R.id.title2);
        title3 = findViewById(R.id.title3);
        //設定進度條
        setupProgressView();
    }
     
    /**
     * 初始化進度條組件
     */
    private void setupProgressView() {
        container.post(new Runnable() {
            @Override
            public void run() {
                int height = container.getMeasuredHeight();
                Log.i(TAG, "容器的最終高度 --> " + height);
                ViewGroup.LayoutParams layoutParams = myProgressView.getLayoutParams();
                layoutParams.height = height;
                myProgressView.setLayoutParams(layoutParams);
                myProgressView.setHeight(height);
                //設定進度條圓點位置
                int padding = container.getPaddingTop();
                float y1 = (title1.getTop() + title1.getBottom() - 2 * padding) >> 1;
                float y2 = (title2.getTop() + title2.getBottom() - 2 * padding) >> 1;
                float y3 = (title3.getTop() + title3.getBottom() - 2 * padding) >> 1;
                myProgressView.setStartY(y1);
                myProgressView.setCirclePositions(new float[]{y1, y2, y3});
            }
        });
        //設定進度
        myProgressView.setProgress(2);
    }
}

結尾碎碎念

能有這篇文章真的是歪打正著,實際上踩過的坑比描述的還要多,有一些礙于篇幅就沒有談到,而且實際上的開發程序完全是…歪曲且有趣?實際情況是這樣的:首先接到這個任務,撰寫完第一版進度條,也就是對應內容標題使用測驗為止的版本,此時恰好由于UI占滿了剩余空間,導致我沒有發現問題,誤以為輕松完成了任務(笑),之后被要求在外層添加滑動布局,此時才發現問題,于是才有了之后的研究,也就有了這篇文章,

另外,如果有更優解,歡迎分享討論,共同學習進步,

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

標籤:其他

上一篇:Android&IOS APP啟動速度專項測驗方法

下一篇:GitHub無法訪問,連接超時

標籤雲
其他(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