このシリーズを2つのパートに分けて、Androidでの画像の色変換と画像の形状変換の入門知識について説明します。
RGBAモデル
RGBAは略語であり、その意味は赤、緑、青、Aplhaです。つまり、赤、緑、青の3つの原色と、透明度の4つのオプションです。私たちが通常目にするさまざまな色は、異なる比率で混合された3つの原色だけであり、アルファは画像の透明度を制御できます。
画像処理におけるいくつかの3つの重要な概念は次のとおりです。
- 色相/色相:オブジェクトによって送信される色
- 彩度:0%(灰色)から100%(彩度)までの色の純度。
- 明るさ/明度:色の相対的な明るさと暗さ
Androidでは、システムは画像の3つの重要な属性を調整するのに役立つクラスColorMatrixを提供します
トーン
setRotateメソッドを使用して画像全体の色相を設定します。1つ目は設定する必要のある色です。0はRに対応し、1はGに対応し、2はBに対応します。ここでは簡単にするために、同じ値の色相を設定します。
ColorMatrix hueMatrix = new ColorMatrix();
hueMatrix.setRotate(0,hue); //R
hueMatrix.setRotate(1,hue); //G
hueMatrix.setRotate(2,hue); //B
飽和
setSaturationメソッドを使用して画像の彩度を設定します。値を設定することにより、画像の彩度を制御できます。
ColorMatrix saturationMatrix = new ColorMatrix();
saturationMatrix.setSaturation(saturation);
輝度
setScaleメソッドを使用して画像の明るさを設定します。4つのパラメータは、Rの明るさ、Gの明るさ、Bの明るさ、透明度の明るさです。
ColorMatrix lumMatrix = new ColorMatrix();
lumMatrix.setScale(lum,lum,lum,1);
ColorMatrixメソッドを使用して画像効果を調整します
次に、Androidプロジェクトを作成して、画像処理の使用方法の学習を開始します。
メインインターフェース
まず、メインメニューを作成して、画像の変更を選択します。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="40dp"
android:textSize="40sp"
android:text="美图秀秀"/>
<Button
android:id="@+id/btn_primary_color"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="PRIMARY COLOR"/>
<Button
android:id="@+id/btn_color_matrix"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="COLOR MATRIX"/>
<Button
android:id="@+id/btn_3"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="等待实现"/>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private Button primaryButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
primaryButton = (Button)findViewById(R.id.btn_primary_color);
primaryButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
primaryColorEvent();
}
});
}
public void primaryColorEvent(){
Intent intent = new Intent(MainActivity.this,PrimaryColorActivity.class);
startActivity(intent);
}
}
3原色調整インターフェース
大まかに分析することができます。画像を調整する場合は、次の3つの手順を実行する必要があります。
- 写真を撮る
- 画像に一連の変更を加える
- 写真を返す
したがって、画像関連の処理を実行するツールクラスを設計することもできます。これにより、コードの再利用性を向上させることができます。
public class ImageUtils {
public static Bitmap handleImageEffect(Bitmap bitmap,float hue,float saturation,float lum){
//由于不能直接在原图上修改,所以创建一个图片,设定宽度高度与原图相同。为32位ARGB图片
Bitmap currentBitmap = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);
//创建一个和原图相同大小的画布
Canvas canvas = new Canvas(currentBitmap);
//创建笔刷并设置抗锯齿
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//色相ColorMatrix
ColorMatrix hueMatrix = new ColorMatrix();
hueMatrix.setRotate(0,hue);
hueMatrix.setRotate(1,hue);
hueMatrix.setRotate(2,hue);
//饱和度ColorMatrix
ColorMatrix saturationMatrix = new ColorMatrix();
saturationMatrix.setSaturation(saturation);
//亮度ColorMatrix
ColorMatrix lumMatrix = new ColorMatrix();
lumMatrix.setScale(lum,lum,lum,1);
//将三种效果融合起来
ColorMatrix imageMatrix = new ColorMatrix();
imageMatrix.postConcat(hueMatrix);
imageMatrix.postConcat(saturationMatrix);
imageMatrix.postConcat(lumMatrix);
paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
canvas.drawBitmap(bitmap,0,0,paint);
return currentBitmap;
}
}
ここでは、Canvasクラスが使用されています。canvasクラスはcanvasクラスであり、後続の操作は元の画像ではなく、canvasで実行されます。3つのColorMatrixを確立し、操作のさまざまな側面を実行し、postConcatメソッドを使用してこれらのMatrixをマージしました。setColorFilterメソッドを使用してペイントの関連プロパティを変更し、キャンバスに画像を描画します。最後に、変更した画像を返します。
インターフェースに戻ります。このデザインでは、3つのシークバーを使用して、色相、彩度、明るさの3つの属性をそれぞれ制御します。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/image_view"
android:layout_width="300dp"
android:layout_height="300dp"
android:scaleType="centerCrop"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
android:layout_centerHorizontal="true"/>
<SeekBar
android:id="@+id/seekbar_hue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_below="@id/image_view"/>
<SeekBar
android:id="@+id/seekbar_saturation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_below="@id/seekbar_hue"/>
<SeekBar
android:id="@+id/seekbar_lum"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_below="@id/seekbar_saturation"/>
</RelativeLayout>
次に、PrimaryColorActivityを編集します。ここでは、最大値と中間値を定義します。これらは中間値から変更でき、一連の方法で色相、彩度、明るさの値を取得します。
public class PrimaryColorActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener{
private ImageView mImageView;
private SeekBar mSeekbarHue,mSeekbarSaturation,mSeekbarLum;
private final static int MAX_VALUE = 255; //最大值
private final static int MID_VALUE = 127; //中间值
private float mHue,mSaturation,mLum;
private Bitmap bitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_primary_color);
//获取图片
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test1);
mImageView = (ImageView)findViewById(R.id.image_view);
mImageView.setImageBitmap(bitmap);
mSeekbarHue = (SeekBar)findViewById(R.id.seekbar_hue);
mSeekbarSaturation = (SeekBar)findViewById(R.id.seekbar_saturation);
mSeekbarLum = (SeekBar)findViewById(R.id.seekbar_lum);
mSeekbarHue.setOnSeekBarChangeListener(this);
mSeekbarSaturation.setOnSeekBarChangeListener(this);
mSeekbarLum.setOnSeekBarChangeListener(this);
mSeekbarHue.setMax(MAX_VALUE);
mSeekbarSaturation.setMax(MAX_VALUE);
mSeekbarLum.setMax(MAX_VALUE);
mSeekbarHue.setProgress(MID_VALUE);
mSeekbarSaturation.setProgress(MID_VALUE);
mSeekbarLum.setProgress(MID_VALUE);
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
switch (seekBar.getId()){
case R.id.seekbar_hue:
//将0-255的值转换为色调值
mHue = (progress - MID_VALUE)*1.0F/MID_VALUE*180;
break;
case R.id.seekbar_saturation:
//将0-255值转换为饱和度值
mSaturation = progress*1.0F/MID_VALUE;
break;
case R.id.seekbar_lum:
//将0-255的值转换为亮度值
mLum = progress*1.0F/MID_VALUE;
break;
}
mImageView.setImageBitmap(ImageUtils.handleImageEffect(bitmap,
mHue,mSaturation,mLum));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
実行後、シークバーをドラッグすると、画像の属性が正常に変更されたことがわかります。
主成分分析-行列変換
以前はColorMatrixクラスを使用していましたが、ご存知のとおり、Matrixは行列を意味するため、ここでは実際に行列を操作して画像を処理しています。Androidは、画像効果の処理に役立つカラーマトリックスを図に示しています。画像の各ポイントは、R、G、B、A、および1で構成されるマトリックスコンポーネントです。
このカラーマトリックスにピクセルに対応するカラーマトリックスコンポーネントを掛けて、新しいマトリックスR1G1B1A1を取得します。図に示すように:
このようにして、カラーマトリックス(色調整の効果)を介してピクセルを新しいピクセルに変換します。
図に示されている行列は、元のピクセルを乗算した後も変更されないため、初期化行列と呼ばれます。
このマトリックスを見てみましょう。元のマトリックスに基づいて、2つの場所の0が100に変更されます。その結果、元のRG値がプラス100値(各ピクセルのRG)に変更されます。値はすべて100ずつ増加します:
同様に、このような行列を見ることができます。元の初期化行列に基づいて、行列Gの1が2に変更されます。これを取り込むと、Gが元の値を2倍にしたことがわかります。効果は次のとおりです。画像全体の緑が2倍になります
カラーマトリックスの4つの行は、それぞれピクセルのRGBAの4つの属性を制御し、カラーマトリックスの5番目の列は、カラーオフセットと呼ばれ、特定の色の係数を直接変更しないことがわかります。ただし、元の色に基づいて全体の色を調整します。
色を変更する必要があります。オフセットを変更できるだけでなく、色係数も変更できます。
行列変換を使用して画像効果を調整します
元のプロジェクトに基づいて、アクティビティを追加し、MainActivityの2番目のボタンをこのアクティビティにジャンプさせます。
1つ目はレイアウトです。次のレイアウトを作成します。GridLayoutでマトリックスを表すために20個のEditTextを追加し、ボタンを使用してマトリックスを適用します。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"/>
<GridLayout
android:id="@+id/group"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3"
android:rowCount="4"
android:columnCount="5"
</GridLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_change"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="改变"/>
<Button
android:id="@+id/btn_reset"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="重置"/>
</LinearLayout>
</LinearLayout>
<
次に、Activityのコードを変更して、EditTextをGridTextに動的に追加し、ColorMatrixをマップします。EditTextを変更して画像効果を変更することにより、ColorMatrixを変更します。
public class ColorMatrixActivity extends AppCompatActivity {
private ImageView mImageView;
private GridLayout mGroup;
private Bitmap bitmap;
private int mEtWidth,mEtHeight;
private EditText[] mEditTexts = new EditText[20];
private float[] mColorMatrix = new float[20]; //对应矩阵
private Button changeButton;
private Button resetButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_color_matrix);
bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.test1);
mImageView = (ImageView) findViewById(R.id.image_view);
mImageView.setImageBitmap(bitmap);
mGroup = (GridLayout) findViewById(R.id.group);
//动态创建EditText,填充GridLayout
//由于在onCreate中,mGroup还没有创建完成,无法获取宽高
//所以通过post方法,在控件绘制完毕后,执行Runnable的具体方法
mGroup.post(new Runnable() {
@Override
public void run() {
mEtWidth = mGroup.getWidth()/5;
mEtHeight = mGroup.getHeight()/4;
addEditText();
initMatrix();
}
});
changeButton = (Button)findViewById(R.id.btn_change);
changeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
changeButtonEvent();
}
});
resetButton = (Button)findViewById(R.id.btn_reset);
resetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
resetButtonEvent();
}
});
}
private void addEditText(){
for (int i=0;i<20;i++){
EditText editText = new EditText(ColorMatrixActivity.this);
mEditTexts[i] = editText;
mGroup.addView(editText,mEtWidth,mEtHeight);
}
}
private void initMatrix(){
for (int i=0;i<20;i++){
if (i%6 == 0){
//i为第0、6、12、18位时
mColorMatrix[i]=1;
mEditTexts[i].setText(String.valueOf(1));
}else{
mColorMatrix[i]=0;
mEditTexts[i].setText(String.valueOf(0));
}
}
}
private void getMatrix(){
for (int i=0;i<20;i++){
mColorMatrix[i] = Float.valueOf(mEditTexts[i].getText().toString());
}
}
private void setImageMatrix(){
Bitmap currentBitmap = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.set(mColorMatrix);
Canvas canvas = new Canvas(currentBitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
canvas.drawBitmap(bitmap,0,0,paint);
mImageView.setImageBitmap(currentBitmap);
}
public void changeButtonEvent(){
getMatrix();
setImageMatrix();
}
public void resetButtonEvent(){
initMatrix();
setImageMatrix();
}
}
ご覧のとおり、画像効果は正常に変更され、効果は図に示されています
これを学んだ後、色相、彩度、明るさを変更するためにそのようなパラメータを使用した理由に答えることができます。明るさを例にとり、setScaleメソッドのソースコードを見てみましょう。ColorMatrixも内部でこのようなカラー配列を使用していることがわかります。同時に、6の倍数を対応する値に設定して明るさを変更します。これは、カラーマトリックスの明るさを変更したい場合に、各色の明るさを変更するだけで、同時に値を上げることができます。
/**
* Set this colormatrix to scale by the specified values.
*/
public void setScale(float rScale, float gScale, float bScale,float aScale) {
final float[] a = mArray;
for (int i = 19; i > 0; --i) {
a[i] = 0;
}
a[0] = rScale;
a[6] = gScale;
a[12] = bScale;
a[18] = aScale;
}
これらの研究を通じて、次のように結論付けることができます。
実際、画像処理は、画像に対するさまざまなカラーマトリックスの処理効果を研究することです。
たとえば、下図の設定方法で、一部の画像処理アプリに共通するノスタルジックな効果を得ることができます。
ピクセルによる画像処理
画像が拡大されると、ドットマトリックスが表示され、各ドットは実際にはピクセルになります。RGBの色比により、さまざまな色を表示できます。
以下は、画像の特殊効果を形成するためのピクセル処理の例です。
負の効果
ABCの3ピクセルの場合、点Bのフィルム効果を見つけるためのアルゴリズムは次のとおりです。実際、逆色は座標点ごとに計算され、次のようになります。
Br = 255-Br;
Bg = 255-Bg;
Bb = 255-Bb;
古い写真効果
古い写真のピクセルへの影響を見つけるためのアルゴリズムは次のとおりです。ここで、pixRは現在のピクセルのR値です。
newR =(int)(0.393 * pixR + 0.769 * pixG + 0.189 * pixB);
newG =(int)(0.349 * pixR + 0.686 * pixG + 0.168 * pixB);
newB =(int)(0.272 * pixR + 0.534 * pixG + 0.131 * pixB);
浮き彫り効果
3点ABCの場合、点Bの緩和効果を見つけるためのアルゴリズムは次のとおりです。
Br = Cr-Br + 127;
Bg = Cg-Bg + 127;
Bb = Cb-Bb + 127;
次に、ピクセルを変更して画像の表示効果を変更します。
最初に新しいアクティビティを作成し、MainActivityの3番目のボタンにジャンプするメソッドを追加します。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<ImageView
android:id="@+id/image_view1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<ImageView
android:id="@+id/image_view2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<ImageView
android:id="@+id/image_view3"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<ImageView
android:id="@+id/image_view4"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
</LinearLayout>
対応する特殊効果メソッドを追加します
さまざまな処理を行うために、ImageUtilsにいくつかの新しいメソッドを追加しました
反転色効果
コードは次のとおりです。画像に対応するピクセルの新しい配列を作成し、getPixelsメソッドを使用してすべてのピクセルを取得します。
getPixelsの2番目のパラメーターは開始点を表すオフセットであり、3番目のパラメーターは配列を読み取るときの行間隔を制御することです。通常は幅が使用されます。後の2つのパラメーターは、最初に読み取られたピクセルポイントの座標を表します。時間、および最後から2番目のパラメーターは、ビットマップから読み取った幅を表し、最後のパラメーターは読み取った高さです。
次に、各ピクセルのColorクラスの赤緑青アルファメソッドを使用してrgbaの4つの値を取得し、アルゴリズムを使用してそのrgb値を変更し、Colorのargbメソッドを使用して新しいピクセル配列に変換します。rgb値を変更するときは、0〜255の制限を超えているかどうかを判断し、超えている場合は255または0に割り当てる必要があることに注意してください。
Bitmap currentBitmap = Bitmap.createBitmap(width,height,
Bitmap.Config.ARGB_8888);
int[] oldPx = new int[width*height]; //存储像素点数组
int[] newPx = new int[width*height];
bitmap.getPixels(oldPx,0,width,0,0,width,height);
for (int i=0;i<width*height;i++){
color = oldPx[i];
r = Color.red(color);
g = Color.green(color);
b = Color.blue(color);
a = Color.alpha(color);
//通过算法计算新的rgb值
r = 255 - r;
g = 255 - g;
b = 255 - b;
if (r > 255) r=255;
else if (r < 0) r=0;
if (g > 255) g=255;
else if (g < 0) g=0;
if (b > 255) b=255;
else if (b < 0) b=0;
newPx[i] = Color.argb(a,r,g,b);
}
currentBitmap.setPixels(newPx,0,width,0,0,width,height);
return currentBitmap;
}
古い写真効果
他のコードは基本的に以前と同じですが、アルゴリズムがわずかに変更されており、元のrgbに基づいて変更することはできません。
public static Bitmap handleImageOldpicture(Bitmap bitmap){
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int color;
int r,g,b,a;
Bitmap currentBitmap = Bitmap.createBitmap(width,height,
Bitmap.Config.ARGB_8888);
int[] oldPx = new int[width*height]; //存储像素点数组
int[] newPx = new int[width*height];
bitmap.getPixels(oldPx,0,width,0,0,width,height);
for (int i=0;i<width*height;i++){
color = oldPx[i];
r = Color.red(color);
g = Color.green(color);
b = Color.blue(color);
a = Color.alpha(color);
int r1,g1,b1;
r1 = (int)(0.393 * r + 0.769 * g + 0.189 * b);
g1 = (int)(0.349 * r + 0.686 * g + 0.168 * b);
b1 = (int)(0.272 * r + 0.534 * g + 0.131 * b);
if (r1 > 255) r1=255;
else if (r1 < 0) r1=0;
if (g1 > 255) g1=255;
else if (g1 < 0) g1=0;
if (b1 > 255) b1=255;
else if (b1 < 0) b1=0;
newPx[i] = Color.argb(a,r1,g1,b1);
}
currentBitmap.setPixels(newPx,0,width,0,0,width,height);
return currentBitmap;
}
浮き彫り効果
前のものと同様に、注意すべき唯一のことは、前のピクセルの色を使用する必要があるため、1から循環してから、対応するアルゴリズムを使用して画像を取得する必要があることです。
public static Bitmap handleImagePixelsRelief(Bitmap bitmap){
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int color,colorBefore;
int r,g,b,a;
int r1,g1,b1;
Bitmap currentBitmap = Bitmap.createBitmap(width,height,
Bitmap.Config.ARGB_8888);
int[] oldPx = new int[width*height]; //存储像素点数组
int[] newPx = new int[width*height];
bitmap.getPixels(oldPx,0,width,0,0,width,height);
for (int i=1;i<width*height;i++){
//取出前一个点的颜色
colorBefore = oldPx[i-1];
r = Color.red(colorBefore);
g = Color.green(colorBefore);
b = Color.blue(colorBefore);
a = Color.alpha(colorBefore);
color = oldPx[i];
r1 = Color.red(color);
g1 = Color.green(color);
b1 = Color.blue(color);
r = (r - r1 + 127);
g = (g - g1 + 127);
b = (b - b1 + 127);
if (r1 > 255) r1=255;
if (g1 > 255) g1=255;
if (b1 > 255) b1=255;
newPx[i] = Color.argb(a,r,g,b);
}
currentBitmap.setPixels(newPx,0,width,0,0,width,height);
return currentBitmap;
}
効果を見る
アクティビティで対応するメソッドを呼び出して、効果を確認します。
public class PixelEffectActivity extends AppCompatActivity {
private ImageView imageView1,imageView2,imageView3,imageView4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pixel_effect);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test1);
imageView1 = (ImageView) findViewById(R.id.image_view1);
imageView2 = (ImageView) findViewById(R.id.image_view2);
imageView3 = (ImageView) findViewById(R.id.image_view3);
imageView4 = (ImageView) findViewById(R.id.image_view4);
imageView1.setImageBitmap(bitmap);
imageView2.setImageBitmap(ImageUtils.handleImageNegative(bitmap));
imageView3.setImageBitmap(ImageUtils.handleImageOldpicture(bitmap));
imageView4.setImageBitmap(ImageUtils.handleImagePixelsRelief(bitmap));
}
}
効果を図に示します
この記事 はオープンソースプロジェクトに含まれています:https://github.com/Android-Alvin/Android-LearningNotesには、さまざまな方向への自己学習プログラミングルート、インタビューの質問収集/顔の経典、および一連の技術が含まれています記事など。リソースは継続的に更新されています…