多媒體教程

1. 計算機圖片的大小

首先咱們打開電腦中的畫圖板,截取像素800*400的區域,在圖中做畫: 
這裏寫圖片描述android

點擊左上角另存爲,有以下幾種常見的圖片格式: 
這裏寫圖片描述數據庫

咱們以bmp格式保存圖片,保存圖片的類型以下: 
這裏寫圖片描述canvas

分別以24位位圖,單色位圖,16色位圖,256色位圖分別保存成4個文件: 
這裏寫圖片描述api

查看以上幾個圖片文件,發現圖片的大小不一致,那麼圖片的總大小是如何計算的呢?如下是計算公式:網絡

圖片的總大小 = 圖片的總像素 * 每一個像素的大小 
每一個像素的大小取決於圖片能表示的顏色的數量。框架

1.1. 單色圖

只能表示黑白兩種顏色,使用0和1就足夠表示了,每一個像素須要一個長度爲1的二進制數字表示顏色即每一個像素佔用1/8個字節。根據公式,上面保存的單色的圖片的大小爲: 
400*800*(1/8) = 40000字節,查看單色圖片的屬性如1下圖: 
這裏寫圖片描述ide

發現圖片的總大小爲40062字節,爲何會比咱們計算的值大呢?由於圖片還要存儲一些額外的信息,好比時間等等。佈局

1.2. 16色圖

只能表示16種顏色,使用16個數字(0 - 15),換成二進制爲0000 - 1111,每一個像素須要一個長度爲4的二進制數字能表示顏色即一個像素佔1/2個字節。post

1.3. 256色圖

只能表示256種顏色,使用256個數字(-128 ~ 127),換成二進制位0000 0000-1111 1111,一個像素佔1個字節。this

1.4. 24位圖

24位圖表示範圍爲24位個二進制數,咱們利用計算器能夠看到,24位最大能夠表示16777215個數字,因此24位圖能表一千六百多萬種顏色。 
這裏寫圖片描述 
每一個像素佔用24位,也就是3個字節,分別用RGB表示: 
R:0 - 255,使用1個字節就能夠表示; 
G:同上; 
B:同上。

1.5. 32位色

每一個像素佔用4個字節,分別用ARGB表示: 
A:透明度,0 - 255 
若是圖片要顯示到界面,那麼內存中須要保存圖片的全部像素的顏色信息,內存中使用ARGB保存。

2. 加載大圖片到內存

2.1. 內存溢出

Android系統以ARGB表示每一個像素,因此每一個像素佔用4個字節,很容易內存溢。下面,使用ImageView加載SD卡中的一張大內存的圖片,該圖片大小以下圖: 
這裏寫圖片描述

界面佈局:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

MainActivity中獲取到圖片的bitmap對象,利用ImageView顯示:

public class MainActivity extends Activity {
    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView iv = (ImageView) findViewById(R.id.iv);
        BitmapFactory.decodeFile("/mnt/sdcard/dog.jpg");
        iv.setImageBitmap(bitmap);
    }
}

運行後,程序會崩潰,查看Logcat日誌輸出,發現內存溢出以下圖: 
這裏寫圖片描述

爲何會出現內存溢出呢?當建立模擬器的時候,有一個VM Heap選項,這個選項表明手機模擬器給每一個應用默認分配的內存大小,當一個應用的使用內存超過了16M,那麼就會報內存溢出的錯誤。 
這裏寫圖片描述

繼續查看日誌,能夠看到有這麼一行日誌,以下圖: 
這裏寫圖片描述

上圖的日誌是說建立一個30720012字節的文件時內存溢出。咱們來計算下加載的圖片顯示到手機上的總的大小,圖片的分辨率爲2400*3200以下圖: 
這裏寫圖片描述 
在安卓中,是使用ARGB表示圖片像素的,因此一個像素是4個byte,根據計算公式,該圖片的總大小爲2400*3200*4=30720000,發現和日誌輸出中的數值是一致的。

2.2. 圖片大小縮放

1.獲取屏幕寬高

//經過Context的getWindowManager()方法獲取Window的管理者對象
Display dp = getWindowManager().getDefaultDisplay();
int screenWidth = dp.getWidth();
int screenHeight = dp.getHeight();

另外一種獲取Windowmanager對象的方法:

WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);

2.獲取圖片寬高

Options opts = new Options();
//inJustDecodeBounds屬性值爲true,表示只請求圖片屬性,不申請內存
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile("sdcard/dog.jpg", opts);
//獲取圖片的寬
int imageWidth = opts.outWidth;
//獲取圖片的高
int imageHeight = opts.outHeight;

3.獲取縮放比例 
圖片的寬高除以屏幕寬高,算出寬和高的縮放比例,取較大值做爲圖片的縮放比例。

int scale = 1;
int scaleX = imageWidth / screenWidth;
int scaleY = imageHeight / screenHeight;
if(scaleX >= scaleY && scaleX > 1){
    scale = scaleX;
}
else if(scaleY > scaleX && scaleY > 1){
    scale = scaleY;
}

4.按縮放比例加載圖片

//設置縮放比例
opts.inSampleSize = scale;
//inJustDecodeBounds屬性爲false,表示爲圖片申請內存
opts.inJustDecodeBounds = false;
//從文件中獲取Bitmap,參數1表示文件路徑,參數2表示圖片參數。
Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts);
iv.setImageBitmap(bm);

運行效果: 
這裏寫圖片描述

3. 建立圖片副本

咱們在使用美圖秀秀對圖片進行編輯操做後,保存圖片的時候,是保存爲另一張圖片。其實其內部原理是先建立一張原圖的副本,而後再副本圖片上進行用戶編輯操做,因此最後保存的時候是新的一張圖片。 
此外,直接加載的bitmap對象是隻讀的,沒法修改,要修改圖片只能在內存中建立出一個如出一轍的bitmap副本,而後修改副本。 
首先,建立佈局,有兩個ImageView:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <ScrollView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            <ImageView
                android:id="@+id/iv_src"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <ImageView
                android:id="@+id/iv_copy"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>

    </ScrollView>

</LinearLayout>

Activity中,建立圖片副本,修改副本圖片,並顯示到另外一個ImageView上。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView iv_copy = (ImageView) findViewById(R.id.iv_copy);  
        ImageView iv_src = (ImageView) findViewById(R.id.iv_src);  
        Bitmap srcBitmap = BitmapFactory.decodeFile("mnt/sdcard/meinv.png");
        iv_src.setImageBitmap(srcBitmap);
        //建立一個和原圖大小同樣,而且圖片參數同樣的空白bitmap
        Bitmap copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(),srcBitmap.getHeight(), srcBitmap.getConfig());
        //建立畫布對象
        Canvas canvas = new Canvas(copyBitmap);
        //建立畫筆對象
        Paint paint = new Paint();
        //調用畫布對象Canvas的drawBitmap()方法將原圖的Bitmap畫到畫布上,參數1表示原圖的bitmap,參數2表示矩陣,參數3表示畫筆
        canvas.drawBitmap(srcBitmap, new Matrix(), paint);
        //將拷貝圖片的bitmap對象上座標(20,30)點設置成紅色
        copyBitmap.setPixel(20,30, Color.RED); 
        //將建立的拷貝的bitmap對象設置到另外一個ImageView上
        iv_copy.setImageBitmap(copyBitmap);
    }
}

運行效果: 
這裏寫圖片描述

4. 對圖片進行特效處理

4.1. 旋轉效果

Matrix matrix = new Matrix();
//讓矩陣旋轉30度
matrix.setRotate(30);
//參數1表示旋轉的角度,參數2和參數3是旋轉的中心點
matrix.setRotate(30, copyBitmap.getWidth()/2, copyBitmap.getHeight()/2);
//調用畫布對象Canvas的drawBitmap()方法將原圖的Bitmap畫到畫布上,參數1表示原圖的bitmap,參數2表示矩陣,參數3表示畫筆
canvas.drawBitmap(srcBitmap, matrix, paint);
iv_copy.setImageBitmap(copyBitmap);

運行效果: 
這裏寫圖片描述

4.2. 平移效果

Matrix matrix = new Matrix();
matrix.setTranslate(20, 0); 
//調用畫布對象Canvas的drawBitmap()方法將原圖的Bitmap畫到畫布上,參數1表示原圖的bitmap,參數2表示矩陣,參數3表示畫筆
canvas.drawBitmap(srcBitmap, matrix, paint);
iv_copy.setImageBitmap(copyBitmap);

運行效果: 
這裏寫圖片描述

4.3. 縮放效果

Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
//調用畫布對象Canvas的drawBitmap()方法將原圖的Bitmap畫到畫布上,參數1表示原圖的bitmap,參數2表示矩陣,參數3表示畫筆
canvas.drawBitmap(srcBitmap, matrix, paint);
iv_copy.setImageBitmap(copyBitmap);

運行效果:這裏寫圖片描述

4.4. 鏡面

Matrix matrix = new Matrix();
//設置縮放
matrix.setScale(-1.0f, 1.0f);
//設置位移
matrix.postTranslate(copyBitmap.getWidth(), 0);
//調用畫布對象Canvas的drawBitmap()方法將原圖的Bitmap畫到畫布上,參數1表示原圖的bitmap,參數2表示矩陣,參數3表示畫筆
canvas.drawBitmap(srcBitmap, matrix, paint);
iv_copy.setImageBitmap(copyBitmap);

注意:setXXX方法每次修改都是最新的的操做,會覆蓋上一次操做,post是在上一次修改的基礎上繼續修改。

運行效果: 
這裏寫圖片描述

4.5. 倒影

Matrix matrix = new Matrix();
matrix.setScale(1.0f, -1.0f);
matrix.postTranslate(0, copyBitmap.getHeight());
//調用畫布對象Canvas的drawBitmap()方法將原圖的Bitmap畫到畫布上,參數1表示原圖的bitmap,參數2表示矩陣,參數3表示畫筆
canvas.drawBitmap(srcBitmap, matrix, paint);
iv_copy.setImageBitmap(copyBitmap);

運行效果: 
這裏寫圖片描述

5. 美圖秀秀案例

本案例模擬實現安卓版的美圖秀秀中的功能,拖動SeekBar,圖片的顏色值隨着變化。 
界面效果: 
這裏寫圖片描述

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="青------紅" />
    <SeekBar
        android:id="@+id/sb_red"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="紫------綠" /> 
    <SeekBar
        android:id="@+id/sb_green"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="黃------藍" />
    <SeekBar
        android:id="@+id/sb_blue"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

MainActivity中初始化控件,給SeekBar設置監聽而且建立原圖的一個副本:

public class MainActivity extends Activity implements OnSeekBarChangeListener {
    private Paint paint;
    private Canvas canvas;
    private Bitmap srcBitmap;
    private ImageView iv;
    private Bitmap copyBitmap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv = (ImageView) findViewById(R.id.iv);
        SeekBar sb_blue = (SeekBar) findViewById(R.id.sb_blue);
        SeekBar sb_green = (SeekBar) findViewById(R.id.sb_green);
        SeekBar sb_red = (SeekBar) findViewById(R.id.sb_red);
        sb_blue.setOnSeekBarChangeListener(this);
        sb_green.setOnSeekBarChangeListener(this);
        sb_red.setOnSeekBarChangeListener(this);
        srcBitmap = BitmapFactory.decodeFile("mnt/sdcard/meinv.png");
        copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
        canvas = new Canvas(copyBitmap);
        paint = new Paint();
        canvas.drawBitmap(srcBitmap, new Matrix(), paint);
        iv.setImageBitmap(copyBitmap);
    }
}

實現SeekBar監聽:

//當進度改變的時候調用
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
 boolean fromUser) {
}
//當開始拖動的時候調用
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
//當中止拖動的時候調用
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
    int id = seekBar.getId();
    //獲取當前SeekBar的進度
    int progress = seekBar.getProgress();
    //建立一個顏色矩陣對象
    ColorMatrix cm = new ColorMatrix();
    float rf = 0;
    float gf = 0;
    float bf = 0;
    switch (id) {
    case R.id.sb_red: 
        rf = progress / 128.0f;
        break;
    case R.id.sb_green:
        gf = progress / 128.0f;
        break;
    case R.id.sb_blue:
        bf = progress / 128.0f;
        break;
    }
    /**設置顏色矩陣,顏色矩陣的計算公式以下
        1 0 0 0 0
        0 1 0 0 0
        0 0 1 0 0
        0 0 0 1 0
        New Red Value = 1*128 + 0*128 + 0*128 + 0*0 + 0
        New Blue Value = 0*128 + 1*128 + 0*128 + 0*0 + 0
        New Green Value = 0*128 + 0*128 + 1*128 + 0*0 + 0
        New Alpha Value = 0*128 + 0*128 + 0*128 + 1*0 + 0
    */
    cm.set(new float[] { rf, 0 , 0 , 0, 0,
                          0, gf, 0 , 0, 0, 
                          0, 0 , bf, 0, 0,
                          0, 0 , 0 , 1, 0 });
    //給Paint對象設置顏色過濾
    paint.setColorFilter(new ColorMatrixColorFilter(cm));
    canvas.drawBitmap(srcBitmap, new Matrix(), paint);
    iv.setImageBitmap(copyBitmap);
}

運行效果: 
這裏寫圖片描述

6. 畫畫板案例

本案例實如今一塊背景上能夠畫畫的功能,相似畫畫板的功能。 
佈局界面:

<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

在MainActivity中,建立一個背景的圖片副本,讓ImageView顯示:

public class MainActivity extends Activity {
    private Paint paint;
    private Canvas canvas;
    private ImageView iv;
    private Bitmap copyBitmap;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv = (ImageView) findViewById(R.id.iv);
        Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.bg);
        copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(),srcBitmap.getHeight(), srcBitmap.getConfig());
        canvas = new Canvas(copyBitmap);
        paint = new Paint();
        canvas.drawBitmap(srcBitmap, new Matrix(), paint);
        //畫一條線,參數1是起點x座標,參數2是起點y座標,參數3是終點x座標,參數4是終點y座標,參數5是畫筆對象。
        canvas.drawLine(10, 10, 40, 100, paint);
        iv.setImageBitmap(copyBitmap);
    }
}

運行效果: 
這裏寫圖片描述

接下來,咱們給ImageView設置觸摸事件,隨着觸摸事件在ImageView上畫畫:

iv.setOnTouchListener(new OnTouchListener() {
    int startX = 0;
    int startY = 0;
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //調用event.getAction()獲取觸摸事件類型
        int action = event.getAction();
        switch (action) {
        //MotionEvent.ACTION_DOWN表示按下事件
        case MotionEvent.ACTION_DOWN:
            //獲取當前手指觸摸的x和y座標
            startX = (int) event.getX();
            startY = (int) event.getY();
            break;
        //MotionEvent.ACTION_MOVE表示手指移動事件
        case MotionEvent.ACTION_MOVE:
            //獲取當前手指觸摸的x和y座標
            int newX = (int) event.getX();
            int newY = (int) event.getY();
            //實時更改畫線的起點座標
            canvas.drawLine(startX, startY, newX, newY, paint);
            startX = newX;
            startY = newY;
            iv.setImageBitmap(copyBitmap);
            break;
        //MotionEvent.ACTION_UP表示擡起事件
        case MotionEvent.ACTION_UP:
            break;
        default:
            break;
        }
        return true;
    }
});

運行效果: 
這裏寫圖片描述

接下來,實現刷子效果,能夠改變畫筆顏色:

public void click1(View view) {
    //設置畫筆粗細
    paint.setStrokeWidth(20);
}
public void click2(View view) {
    //設置畫筆顏色爲綠色
    paint.setColor(Color.GREEN);
}
public void click3(View view) {
    paint.setColor(Color.RED);
}

運行效果: 
這裏寫圖片描述

6.2. 保存圖片

將圖片保存到SD卡:

public void click4(View view){
    File file = new File(Environment.getExternalStorageDirectory().getPath(), 」haha.png」);
        FileOutputStream fos;
        try {
            fos = new FileOutputStream(file);
            //調用Bitmap對象的compress()方法保存圖片,參數1是圖片的格式,參數2是圖片壓縮的質量,參數3是輸出流。
            copyBitmap.compress(CompressFormat.PNG, 100, fos);
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

加入權限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

運行效果: 
這裏寫圖片描述

這裏寫圖片描述

導出到電腦,用圖片查看器查看: 
這裏寫圖片描述

6.3. 在圖庫中顯示圖片

系統每次收到SD卡就緒廣播時,都會去遍歷SD卡的全部文件和文件夾,把遍歷到的全部多媒體文件都在MediaStore數據庫保存一個索引,這個索引包含多媒體文件的文件名、路徑、大小。 
圖庫每次打開時,並不會去遍歷SD卡獲取圖片,而是經過內容提供者從MediaStore數據庫中獲取圖片的信息,而後讀取該圖片。 
系統開機或者點擊掛載SD卡按鈕時,系統會發送sd卡就緒廣播,咱們也能夠手動發送掛載SD卡就緒廣播。

Intent intent = new Intent();
intent.setAction(Intent.ACTION_MEDIA_MOUNTED);
intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory()));
sendBroadcast(intent);

運行效果: 
這裏寫圖片描述

7. 撕衣服案例

本案例實現撕衣服的效果,手指在圖片上滑動,劃過的地方將會把衣服去掉。效果圖以下: 
這裏寫圖片描述

手指在圖片上滑動: 
這裏寫圖片描述

實現原理:在屏幕上面有兩個ImageView,下面的ImageView是沒有穿衣服的圖片,上面的是穿衣服的。而後建立一個穿衣服的圖片的Bitmap副本,設置給上方的ImageView,給上方的ImageView設置觸摸監聽,當手指移動的時候在副本Bitmap上畫透明像素,這樣下方的ImageView就顯示出來了。

1.佈局代碼:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/after19" />
    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

2.具體代碼邏輯:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //上方的ImageView
        final ImageView iv = (ImageView) findViewById(R.id.iv);
        //經過BitmapFactory.decodeResource()方法加載圖片
        Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pre19);
        //建立圖片副本
        final Bitmap alterBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
        Paint paint = new Paint();
        Canvas canvas = new Canvas(alterBitmap);
        canvas.drawBitmap(srcBitmap, new Matrix(), paint);
        //給上方ImageView設置圖片副本背景
        iv.setImageBitmap(alterBitmap);
        //給ImageView設置觸摸事件
        iv.setOnTouchListener(new OnTouchListener() {
            //當手指滑動的時候調用
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    for (int i = -7; i < 7; i++) {
                        for (int j = -7; j < 7; j++) {
                            if (Math.sqrt(i * i + j * j) < 7) {
                                try {
                                    //在圖片副本上畫透明像素
                                    alterBitmap.setPixel((int) event.getX() + i, (int) event.getY() + j, Color.TRANSPARENT);
                                } catch (Exception e) {
                                }
                            }

                        }
                    }
                    //給ImageView設置修改後的背景,實時刷新
                    iv.setImageBitmap(alterBitmap);
                    break;

                }
                return true;
            }
        });
    }
}

8. 音樂播放器案例

本案例實現點擊按鈕實現播放音頻的功能。點擊按鈕播放SD卡上的音頻文件。播放音頻須要使用到MediaPlayer這個api,下圖是MediaPlayer的狀態圖解: 
這裏寫圖片描述

8.1. 播放音頻文件

將xiaopingguo.mp3音頻文件導入到SD卡中,以下圖: 
這裏寫圖片描述

Activity中點擊按鈕播放音樂:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void click(View v) {
        //建立MediaPlayer對象
        final MediaPlayer player = new MediaPlayer();
        //設置播放的類型
        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        try {   
            //設置播放的數據源
            player.setDataSource("/mnt/sdcard/xiaopingguo.mp3");
             //player.setDataSource("http://192.168.116.132:8080/xiaopingguo.mp3");
            //準備播放
            player.prepare();
            //若是播放的是網絡資源,那麼使用prepareAsync()方法來準備播放,由於prepare()方法是阻塞線程的
            // player.prepareAsync();
            //設置準備監聽,當播放準備好後回調onPrepared()方法
            player.setOnPreparedListener(new OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    //開始播放
                    player.start();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

8.2. 完善音樂播放器

在Activity中播放音頻,當按返回鍵回到Home界面後,因爲應用的進程變成了空進程,因此很容易被系統回收。那麼怎麼解決這個問題呢?能夠在Service中操做播放音頻,這樣就不容易被系統回收。

1.建立佈局

這裏寫圖片描述

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click1"
        android:text="播放" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click2"
        android:text="暫停" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click3"
        android:text="繼續播放" />
    <SeekBar
        android:id="@+id/sb_control"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

2.建立播放音頻Service

定義IService接口提供服務中的方法:

public interface Iservice {
    public void callPlay();
    public void callPause();
    public void callReplay();
    public void callSeekTo(int position);
}

編寫Service類,並在Service中定義操做音頻的方法並建立Binder對象:

public class MusicService extends Service {
    private MediaPlayer player;
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }
    @Override
    public void onCreate() {
        player = new MediaPlayer(); 
        super.onCreate();
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
    public void play(){
        //player.reset()方法用來重置MediaPlayer
        player.reset();
        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        try {
            player.setDataSource("/mnt/sdcard/xiaopingguo.mp3");
            player.prepare();
            player.setOnPreparedListener(new OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                   player.start();              
                   //當開始播放時開始更新進度
                   updateSeekBar();
                 }
            });             
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //定義暫停播放的方法
    public void pause(){
        player.pause();
    }
    //定義繼續播放的方法
    public void rePlay(){
        player.start();
    }
    //定義指定從某個地方開始播放
    public void seekTo(int position){
        player.seekTo(position);
    }
    private class MyBinder extends Binder implements Iservice{
        @Override
        public void callPlay() {
            play();
        }
        @Override
        public void callPause() {
            pause();
        }
        @Override
        public void callReplay() {
            rePlay();
        }
        @Override
        public void callSeekTo(int position) {
            seekTo(position);
        }
    }
}

更新進度條:

private void updateSeekBar() {  
    //歌曲的總的時長
    final int duration = player.getDuration();  
    //建立Timer定時任務對象
    Timer timer = new Timer();
    //建立任務對象
    TimerTask task = new TimerTask() {
        @Override
        public void run() {
            //getCurrentPosition()獲取當前播放的位置
            int currentPosition = player.getCurrentPosition(); 
            //建立Message消息對象,將歌曲總長度和當前的位置做爲數據存入Message
            Message msg = Message.obtain();
            Bundle bundle = new Bundle();
            bundle.putInt("duration", duration);
            bundle.putInt("currentPosition", currentPosition);
            msg.setData(bundle);
            //發送消息
            MainActivity.handler.sendMessage(msg);
        }
    };
    //執行定時任務,參數1爲任務對象,參數2爲多久開始執行任務,參數3爲任務執行的間隔時間
    timer.schedule(task, 50, 1000);
}

3.Activity中實現播放音頻,暫停播放,繼續播放和處理更新進度

public class MainActivity extends Activity {
    private Myconn myconn;
    private Iservice iservice;
    private static SeekBar sb_contrller;
    //定義Handler,處理進度更新操做
    public static Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            Bundle data = msg.getData();
            int duration = data.getInt("duration"); 
            int currentPosition = data.getInt("currentPosition");
            //設置SeekBar的總進度
            sb_contrller.setMax(duration); 
            //設置SeekBar的當前位置
            sb_contrller.setProgress(currentPosition);
        };
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sb_contrller = (SeekBar) findViewById(R.id.sb_control);
        //設置SeekBar改變監聽
        sb_contrller.setOnSeekBarChangeListener(new 
          OnSeekBarChangeListener() {
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                int position = seekBar.getProgress();
                //調用服務中的方法,從SeekBar的拖動到的位置開始播放
                iservice.callSeekTo(position);
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {
            }
        });
        Intent intent = new Intent(this, MusicService.class);
        //先調用startService()讓服務能一直運行,再調用bindService()方法使Activity能夠調用服務中的方法
        startService(intent);
        myconn = new Myconn();
        bindService(intent, myconn, BIND_AUTO_CREATE);
    }
    public void click1(View v) {
        iservice.callPlay();
    }
    public void click2(View v) {

        iservice.callPause();
    }
    public void click3(View v) {
        iservice.callReplay();
    }
    private class Myconn implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
         {
            iservice = (Iservice) service;
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }
    @Override
    protected void onDestroy() {
        unbindService(myconn);
        super.onDestroy();
    }
}

運行效果: 
這裏寫圖片描述

9. 視頻播放器

本案例實現播放SD卡中的一個mp4文件的視頻。實現該功能須要用到MediaPlayer中的api,此外還須要SurfaceView來顯示播放的視頻。

9.1. SurfaceView

SurfaceView是用來播放視頻的控件,使用了雙緩衝技術:內存中有兩個畫布,A畫布顯示至屏幕,B畫布在內存中繪製下一幀畫面,繪製完畢後B顯示至屏幕,A在內存中繼續繪製下一幀畫面。 
首先建立佈局,佈局中使用SurfaceView:

<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >    
    <SurfaceView
        android:id="@+id/sv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

在MainActivity中實現播放:

public class MainActivity extends Activity {
    private SurfaceView sfv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sfv = (SurfaceView) findViewById(R.id.sfv);
    }
    public void click(View v) {
        new Thread() {
            public void run() {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                final MediaPlayer player = new MediaPlayer();
                try {
                            player.setDataSource("http://192.168.116.132:8080/test.mp4");
                    player.prepareAsync();
                    //獲取SurfaceHolder對象
                    SurfaceHolder holder = sfv.getHolder();
                    //給MediaPlayer設置holder對象
                    player.setDisplay(holder);
                    //設置準備好的監聽,當準備好了以後調開始播放
                    player.setOnPreparedListener(new OnPreparedListener(){
                        @Override
                        public void onPrepared(MediaPlayer mp) {
                            //開始播放
                            player.start();
                        }
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }
}

運行效果,發現播放不了,以下圖: 
這裏寫圖片描述

這是因爲SurfaceView是重量級組件,對畫面的實時更新要求較高。咱們查看SurfaceView的API文檔,以下圖: 
這裏寫圖片描述

文檔須要咱們實現兩個回調方法,給SurfaceHolder設置CallBack,經過回調能夠知道SurfaceView的狀態,SurfaceView一旦不可見,就會被銷燬,一旦可見,就會被建立,銷燬時中止播放,再次建立時再開始播放。

SurfaceHolder holder = sv.getHolder();
holder.addCallback(new Callback() {
    //當SurfaceView銷燬時調用
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //記錄SurfaceView不可見的時候播放的位置
        lastPosition = player.getCurrentPosition();
        //判斷player是否爲空或者是否正在播放,若是不爲空而且正在播放,須要將player暫停
        if (player != null && player.isPlaying()) {
            player.pause();
        }
    }
    //當SurfaceView建立的時候調用,能夠在這個方法中,建立MediaPlayer對象和準備工做等操做
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            player = new MediaPlayer();
            player.setDataSource("http://192.168.116.132:8080/test.mp4");
            player.setDisplay(holder);
            player.prepareAsync(); 
            player.setOnPreparedListener(new OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    player.start();
                    //調用MediaPlayer的seekTo()方法從指定位置播放,好比當咱們按Home鍵,下次進入的時候須要從以前的位置播放
                    player.seekTo(lastPosition);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //當SurfaceView改變的時候調用
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }
});

運行結果: 
這裏寫圖片描述

9.2. VideoView

VideoView也是播放視頻的一個控件,這個類繼承了SurfaceView。 
界面佈局:

<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

Activity中利用VideoView播放視頻:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        VideoView vv = (VideoView) findViewById(R.id.videoView);
        vv.setVideoPath("http://192.168.116.132:8080/test.mp4");
        vv.start();     
    }
}

運行結果: 
這裏寫圖片描述

9.3. Vitamio框架

因爲系統原生的SurfaceView和VideoView對視頻播放功能的不完整,因此現實開發中,開發人員不多使用這兩個控件。開發中通常都會使用開源框架來實現播放視頻的功能,由於開源框架支持播放視頻的格式比較多,並且功能比較強大。Vitamio是一款Android與iOS平臺上的全能多媒體開發框架,它的官網地址是https://www.vitamio.org。下圖是Vitamio的官網首頁: 
這裏寫圖片描述

下面使用Vitamio框架播放視頻。Vitamio開源框架是以類庫的方式提供給開發者的,實際上就是一個安卓工程,咱們將Vitamio類庫導入到Eclipse,右擊查看項目屬性能夠發現它是一個類庫,以下圖: 
這裏寫圖片描述

那麼咱們的項目如何引入Vitamio類庫呢?右擊項目選擇Properties,以下圖: 
這裏寫圖片描述

選擇Android,而後點擊Add按鈕,以下圖: 
這裏寫圖片描述

選擇vitamio_lib,而後點擊OK按鈕,以下圖: 
這裏寫圖片描述

這時候,類庫就引入到了咱們本身的項目中,以下圖: 
這裏寫圖片描述

這時候項目中就能夠使用vitamio中的api了。 
首先咱們在佈局中使用vitamio提供的播放視頻的控件:

<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <io.vov.vitamio.widget.VideoView
        android:id="@+id/vv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

在MainActivity中使用VideoView播放視頻:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //檢查類庫
        if (!LibsChecker.checkVitamioLibs(this)) {
            return;
        }
        final VideoView vv = (VideoView) findViewById(R.id.vv);
        //設置播放文件資源路徑
        vv.setVideoPath("http://192.168.116.132:8080/test.mp4"); 
        vv.setOnPreparedListener(new OnPreparedListener() {
            //設置準備監聽,當準備好了以後才能夠播放
            @Override
            public void onPrepared(MediaPlayer mp) {
                //開始播放
                vv.start(); 
            }
        });
        //設置控制器
        vv.setMediaController(new MediaController(getApplicationContext()));
    }
}

注意,最後還須要在清單文件中配置一個Activity:

<activity android:name="io.vov.vitamio.activity.InitActivity"></activity>

運行結果: 
這裏寫圖片描述

10. 調用系統相機

10.1. 照相

咱們能夠隱式的開啓系統提供的照相Activity,經過系統照相功能進行拍照。

//建立意圖對象
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//建立照片保存的File對象
File file = new File(Environment.getExternalStorageDirectory(),"paizhao.png"); 
//設置文件保存的Uri
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); 
//開啓Activity
startActivityForResult(intent, 0);

10.2. 錄像

Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
File file = new File(Environment.getExternalStorageDirectory(),"luxiang.3gp"); 
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); 
//設置視頻的質量
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
startActivityForResult(intent, 0);
相關文章
相關標籤/搜索