Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Launcher 設置壁紙

Android Launcher 設置壁紙

編輯:關於Android編程

版本:1.0 日期:2014.11.25 2014.11.26版權:©kince特別推薦:泡在網上的日子
一、概述 一般Launcher都帶有壁紙設置的功能,Android提供了設置壁紙的API,在包android.app下面的類WallpaperInfo和WallpaperManager。動態壁紙所在的包是android.service.wallpaper,要區別開。但是要注意,WallpaperInfo是描述動態壁紙的類,從WallpaperManager類的getWallpaperInfo()方法獲取的返回值就是WallpaperInfo類對象。關於設置靜態壁紙的類,主要就是WallpaperManager這個類。它有setBitmap(Bitmap bitmap)、setResource(int resid)、setStream(InputStream data)等方法設置靜態壁紙。本文主要討論靜態壁紙的設置,包括固定不動和隨著桌面滑動的壁紙。 二、Launcher壁紙設置 下面以KitKat為例,說一下Launcher設置壁紙的流程。在Launcher類的菜單中有設置壁紙的功能,代碼如下:
 @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case MENU_WALLPAPER_SETTINGS :
            startWallpaper();
            return true ;
        }
        return super .onOptionsItemSelected(item);
    }
進入startWallpaper()方法,
private void startWallpaper () {
        showWorkspace( true);
        final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER );
        Intent chooser = Intent.createChooser(pickWallpaper,
                getText(R.string. chooser_wallpaper));
        // NOTE: Adds a configure option to the chooser if the wallpaper supports it
        //       Removed in Eclair MR1
//        WallpaperManager wm = (WallpaperManager)
//                getSystemService(Context.WALLPAPER_SERVICE);
//        WallpaperInfo wi = wm.getWallpaperInfo();
//        if ( wi != null && wi.getSettingsActivity() != null) {
//            LabeledIntent li = new LabeledIntent(getPackageName(),
//                    R.string.configure_wallpaper, 0);
//            li.setClassName(wi.getPackageName(), wi.getSettingsActivity());
//            chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { li });
//        }
        startActivityForResult(chooser, REQUEST_PICK_WALLPAPER );
    }
上面這個方法會傳遞一個選擇壁紙的Intent,然後使用Intent.createChooser打開一個界面選擇具備設置壁紙的應用。\
圖-1
每一個應用都有自己設置壁紙的功能,而且也不盡相同。360是這樣:\
圖-2 原生圖庫是這樣:\
圖-3
那下面就以原生圖庫為例來梳理一下這個流程,還是以KitKat版本為例,找到原生圖庫的代碼Gallery2,地址是https://android.googlesource.com/platform/packages/apps/Gallery2/。不過這個地址有些奇怪,從中下載的代碼不完全,少了幾個類。還是從http://grepcode.com/file/repZ喎?/kf/ware/vc/" target="_blank" class="keylink">vc2l0b3J5LmdyZXBjb2RlLmNvbS9qYXZhL2V4dC9jb20uZ29vZ2xlLmFuZHJvaWQvYW5kcm9pZC1hcHBzLzQuNF9yMS9jb20vYW5kcm9pZC9nYWxsZXJ5M2TV4rj2tdjWt8/C1NjG5Mv7yLHJ2bXEwOCho0dhbGxlcnkytcTUtMLr0rK63Lbgo6yyu7XDsrvLtbnIuOjUrcn6YXBwtcS0+sLrvaHXs9DUtry7ubK7tO2jrLK7ysfSu7DjtcTHv6Gjz+7Ev7mks8y088zlyOfPwqO6PGltZyBzcmM9"/uploadfile/Collfiles/20141127/2014112708521547.png" alt="\" />
圖-4 當然大多數還是負責處理顯示圖片、編輯圖片的功能,對於設置壁紙的功能代碼不是很多。那麼當從選擇的界面中選擇圖庫這個應用後,流程是怎麼樣的呢?之前是發送了一個Intent.ACTION_SET_WALLPAPER的Intent,那麼在圖庫的應用中必然有個Activity接受這個Intent,而且這個Activity的Intent-filter必然有ACTION_SET_WALLPAPER。在圖庫的AndroidManifest.xml文件中,搜索SET_WALLPAPER,發現Wallpaper這個類唯一具備這個條件。那麼很顯然,當我們從選擇界面點擊圖庫後,進入的就是這個Activity。
            
                
                
                
            
            
                
                
            
            
        
來到Wallpaper類下,代碼如下:
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.android.gallery3d.app;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.WallpaperManager;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.Display;

import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.filtershow.crop.CropActivity;
import com.android.gallery3d.filtershow.crop.CropExtras;

import java.lang.IllegalArgumentException;

/**
* Wallpaper picker for the gallery application. This just redirects to the
* standard pick action.
*/
public class Wallpaper extends Activity {
    @SuppressWarnings("unused")
    private static final String TAG = "Wallpaper";

    private static final String IMAGE_TYPE = "image/*";
    private static final String KEY_STATE = "activity-state";
    private static final String KEY_PICKED_ITEM = "picked-item";

    private static final int STATE_INIT = 0;
    private static final int STATE_PHOTO_PICKED = 1;

    private int mState = STATE_INIT;
    private Uri mPickedItem;

    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        if (bundle != null) {
            mState = bundle.getInt(KEY_STATE);
            mPickedItem = (Uri) bundle.getParcelable(KEY_PICKED_ITEM);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle saveState) {
        saveState.putInt(KEY_STATE, mState);
        if (mPickedItem != null) {
            saveState.putParcelable(KEY_PICKED_ITEM, mPickedItem);
        }
    }

    @SuppressWarnings("deprecation")
    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
    private Point getDefaultDisplaySize(Point size) {
        Display d = getWindowManager().getDefaultDisplay();
        if (Build.VERSION.SDK_INT >= ApiHelper.VERSION_CODES.HONEYCOMB_MR2) {
            d.getSize(size);
        } else {
            size.set(d.getWidth(), d.getHeight());
        }
        return size;
    }

    @SuppressWarnings("fallthrough")
    @Override
    protected void onResume() {
        super.onResume();
        Intent intent = getIntent();
        switch (mState) {
            case STATE_INIT: {
                mPickedItem = intent.getData();
                if (mPickedItem == null) {
                    Intent request = new Intent(Intent.ACTION_GET_CONTENT)
                            .setClass(this, DialogPicker.class)
                            .setType(IMAGE_TYPE);
                    startActivityForResult(request, STATE_PHOTO_PICKED);
                    return;
                }
                mState = STATE_PHOTO_PICKED;
                // fall-through
            }
            case STATE_PHOTO_PICKED: {
                Intent cropAndSetWallpaperIntent;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    WallpaperManager wpm = WallpaperManager.getInstance(getApplicationContext());
                    try {
                        cropAndSetWallpaperIntent = wpm.getCropAndSetWallpaperIntent(mPickedItem);
                        startActivity(cropAndSetWallpaperIntent);
                        finish();
                        return;
                    } catch (ActivityNotFoundException anfe) {
                        // ignored; fallthru to existing crop activity
                    } catch (IllegalArgumentException iae) {
                        // ignored; fallthru to existing crop activity
                    }
                }

                int width = getWallpaperDesiredMinimumWidth();
                int height = getWallpaperDesiredMinimumHeight();
                Point size = getDefaultDisplaySize(new Point());
                float spotlightX = (float) size.x / width;
                float spotlightY = (float) size.y / height;
                cropAndSetWallpaperIntent = new Intent(CropActivity.CROP_ACTION)
                    .setClass(this, CropActivity.class)
                    .setDataAndType(mPickedItem, IMAGE_TYPE)
                    .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
                    .putExtra(CropExtras.KEY_OUTPUT_X, width)
                    .putExtra(CropExtras.KEY_OUTPUT_Y, height)
                    .putExtra(CropExtras.KEY_ASPECT_X, width)
                    .putExtra(CropExtras.KEY_ASPECT_Y, height)
                    .putExtra(CropExtras.KEY_SPOTLIGHT_X, spotlightX)
                    .putExtra(CropExtras.KEY_SPOTLIGHT_Y, spotlightY)
                    .putExtra(CropExtras.KEY_SCALE, true)
                    .putExtra(CropExtras.KEY_SCALE_UP_IF_NEEDED, true)
                    .putExtra(CropExtras.KEY_SET_AS_WALLPAPER, true);
                startActivity(cropAndSetWallpaperIntent);
                finish();
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode != RESULT_OK) {
            setResult(resultCode);
            finish();
            return;
        }
        mState = requestCode;
        if (mState == STATE_PHOTO_PICKED) {
            mPickedItem = data.getData();
        }

        // onResume() would be called next
    }
}
這個類的邏輯比較簡單,先是第一次進入Activity然後進入onResume()方法,然後進入 STATE_INIT。在這裡選擇一張照片,然後返回。接著調用onActivityResult方法,用於獲取mPickedItem值,之後再次調用onResume()方法,然後進入STATE_PHOTO_PICKED。先是一個判斷,如果是KitKat以上版本,那麼會調用新的方法getCropAndSetWallpaperIntent去完成壁紙的設置,界面如下:\圖-5 如果是KitKat以下版本,那麼會調用以下方法:
                int width = getWallpaperDesiredMinimumWidth();
                int height = getWallpaperDesiredMinimumHeight();
                Point size = getDefaultDisplaySize( new Point());
                float spotlightX = (float) size.x / width;
                float spotlightY = (float) size.y / height;
                cropAndSetWallpaperIntent = new Intent(CropActivity.CROP_ACTION )
                    .setClass( this, CropActivity.class)
                    .setDataAndType( mPickedItem, IMAGE_TYPE)
                    .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT )
                    .putExtra(CropExtras. KEY_OUTPUT_X, width)
                    .putExtra(CropExtras. KEY_OUTPUT_Y, height)
                    .putExtra(CropExtras. KEY_ASPECT_X, width)
                    .putExtra(CropExtras. KEY_ASPECT_Y, height)
                    .putExtra(CropExtras. KEY_SPOTLIGHT_X, spotlightX)
                    .putExtra(CropExtras. KEY_SPOTLIGHT_Y, spotlightY)
                    .putExtra(CropExtras. KEY_SCALE, true )
                    .putExtra(CropExtras.KEY_SCALE_UP_IF_NEEDED , true)
                    .putExtra(CropExtras.KEY_SET_AS_WALLPAPER , true);
                startActivity(cropAndSetWallpaperIntent);
                finish();
先是調用系統方法獲取期望的壁紙的最小寬度和最小高度。然後是CropExtras這個類,這個實體類描述了剪裁壁紙的信息,包括長寬、縮放、格式等等。代碼如下:
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.android.gallery3d.filtershow.crop;

import android.net.Uri;

public class CropExtras {

    public static final String KEY_CROPPED_RECT = "cropped-rect";
    public static final String KEY_OUTPUT_X = "outputX";
    public static final String KEY_OUTPUT_Y = "outputY";
    public static final String KEY_SCALE = "scale";
    public static final String KEY_SCALE_UP_IF_NEEDED = "scaleUpIfNeeded";
    public static final String KEY_ASPECT_X = "aspectX";
    public static final String KEY_ASPECT_Y = "aspectY";
    public static final String KEY_SET_AS_WALLPAPER = "set-as-wallpaper";
    public static final String KEY_RETURN_DATA = "return-data";
    public static final String KEY_DATA = "data";
    public static final String KEY_SPOTLIGHT_X = "spotlightX";
    public static final String KEY_SPOTLIGHT_Y = "spotlightY";
    public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
    public static final String KEY_OUTPUT_FORMAT = "outputFormat";

    private int mOutputX = 0;
    private int mOutputY = 0;
    private boolean mScaleUp = true;
    private int mAspectX = 0;
    private int mAspectY = 0;
    private boolean mSetAsWallpaper = false;
    private boolean mReturnData = false;
    private Uri mExtraOutput = null;
    private String mOutputFormat = null;
    private boolean mShowWhenLocked = false;
    private float mSpotlightX = 0;
    private float mSpotlightY = 0;

    public CropExtras(int outputX, int outputY, boolean scaleUp, int aspectX, int aspectY,
            boolean setAsWallpaper, boolean returnData, Uri extraOutput, String outputFormat,
            boolean showWhenLocked, float spotlightX, float spotlightY) {
        mOutputX = outputX;
        mOutputY = outputY;
        mScaleUp = scaleUp;
        mAspectX = aspectX;
        mAspectY = aspectY;
        mSetAsWallpaper = setAsWallpaper;
        mReturnData = returnData;
        mExtraOutput = extraOutput;
        mOutputFormat = outputFormat;
        mShowWhenLocked = showWhenLocked;
        mSpotlightX = spotlightX;
        mSpotlightY = spotlightY;
    }

    public CropExtras(CropExtras c) {
        this(c.mOutputX, c.mOutputY, c.mScaleUp, c.mAspectX, c.mAspectY, c.mSetAsWallpaper,
                c.mReturnData, c.mExtraOutput, c.mOutputFormat, c.mShowWhenLocked,
                c.mSpotlightX, c.mSpotlightY);
    }

    public int getOutputX() {
        return mOutputX;
    }

    public int getOutputY() {
        return mOutputY;
    }

    public boolean getScaleUp() {
        return mScaleUp;
    }

    public int getAspectX() {
        return mAspectX;
    }

    public int getAspectY() {
        return mAspectY;
    }

    public boolean getSetAsWallpaper() {
        return mSetAsWallpaper;
    }

    public boolean getReturnData() {
        return mReturnData;
    }

    public Uri getExtraOutput() {
        return mExtraOutput;
    }

    public String getOutputFormat() {
        return mOutputFormat;
    }

    public boolean getShowWhenLocked() {
        return mShowWhenLocked;
    }

    public float getSpotlightX() {
        return mSpotlightX;
    }

    public float getSpotlightY() {
        return mSpotlightY;
    }
}
不過對於類成員屬性我們並不清楚各自代表什麼意思,不過從傳遞進去的參數來看,是可以發現一些端倪的。比如KEY_OUTPUT_X現在可以簡單的理解為width,同理高度也一樣。 進入CropActivity的時候會把這些信息傳遞過去。CropActivity就是上面那個可以縮放設置壁紙的界面,完成這個功能的那個其實是一個自定義View:CropView。接著來到CropActivity,這就是圖-3所顯示的Activity。這個類所在包以及其他類如下:\
圖-6 可以看到共有七個類一起完成壁紙移動、縮放設置的功能,當然最核心的是CropActivity,它負責統籌調度其他類。代碼如下:
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gallery3d.filtershow.crop;

import android.app.ActionBar;
import android.app.Activity;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.Toast;
import com.android.gallery3d.R;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.filtershow.cache.ImageLoader;
import com.android.gallery3d.filtershow.tools.SaveImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Activity for cropping an image.
 */
public class CropActivity extends Activity {
	
    private static final String LOGTAG = "CropActivity";
    public static final String CROP_ACTION = "com.android.camera.action.CROP";
    
    // 剪裁信息
    private CropExtras mCropExtras = null;
    // 加載Bitmap
    private LoadBitmapTask mLoadBitmapTask = null;

    private int mOutputX = 0;
    private int mOutputY = 0;
    
    private Bitmap mOriginalBitmap = null;
    
    private RectF mOriginalBounds = null;
    
    private int mOriginalRotation = 0;
    
    private Uri mSourceUri = null;
    // 自定義的View 剪裁壁紙
    private CropView mCropView = null;
    // 保存的button
    private View mSaveButton = null;
    // 保護IO操作
    private boolean finalIOGuard = false;

    private static final int SELECT_PICTURE = 1; // request code for picker

    private static final int DEFAULT_COMPRESS_QUALITY = 90;
    /**
     * The maximum bitmap size we allow to be returned through the intent.
     * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
     * have some overhead to hit so that we go way below the limit here to make
     * sure the intent stays below 1MB.We should consider just returning a byte
     * array instead of a Bitmap instance to avoid overhead.
     */
    public static final int MAX_BMAP_IN_INTENT = 750000;

    // Flags
    private static final int DO_SET_WALLPAPER = 1;
    private static final int DO_RETURN_DATA = 1 << 1;
    private static final int DO_EXTRA_OUTPUT = 1 << 2;

    private static final int FLAG_CHECK = DO_SET_WALLPAPER | DO_RETURN_DATA | DO_EXTRA_OUTPUT;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 
        Intent intent = getIntent();
        setResult(RESULT_CANCELED, new Intent());
        mCropExtras = getExtrasFromIntent(intent);
        // 
        if (mCropExtras != null && mCropExtras.getShowWhenLocked()) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
        }

        setContentView(R.layout.crop_activity);
        mCropView = (CropView) findViewById(R.id.cropView);

        // 自定義ActionBar布局 添加的Button用於保存設置壁紙
        ActionBar actionBar = getActionBar();
        if (actionBar != null) {
            actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
            actionBar.setCustomView(R.layout.filtershow_actionbar);
           
            View mSaveButton = actionBar.getCustomView();
            mSaveButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                	// 保存設置剪裁的壁紙
                    startFinishOutput();
                }
            });
        }
        if (intent.getData() != null) {
            mSourceUri = intent.getData();
            // 開始加載之前選擇的圖片
            startLoadBitmap(mSourceUri);
        } else {
        	// 否則回去選圖片
            pickImage();
        }
    }

    private void enableSave(boolean enable) {
        if (mSaveButton != null) {
            mSaveButton.setEnabled(enable);
        }
    }

    @Override
    protected void onDestroy() {
    	// 取消加載Bitmap的任務
        if (mLoadBitmapTask != null) {
            mLoadBitmapTask.cancel(false);
        }
        super.onDestroy();
    }

    @Override
    public void onConfigurationChanged (Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mCropView.configChanged();
    }

    /**
     * Opens a selector in Gallery to chose an image for use when none was given
     * in the CROP intent.
     */
    private void pickImage() {
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
                SELECT_PICTURE);
    }

    /**
     * Callback for pickImage().
     */
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) {
            mSourceUri = data.getData();
            startLoadBitmap(mSourceUri);
        }
    }

    /**
     * Gets screen size metric.
     * 通過獲取屏幕的寬和高 返回二者中的最大值 一般的豎屏手機肯定是返回高度了
     */
    private int getScreenImageSize() {
        DisplayMetrics outMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
        return (int) Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
    }

    /**
     * Method that loads a bitmap in an async task.
     * 啟動一個一步任務去加載Bitmap 這個Bitmap就是之前我們選擇要設置為壁紙的圖片
     */
    private void startLoadBitmap(Uri uri) {
        if (uri != null) {
            enableSave(false);
            final View loading = findViewById(R.id.loading);
            loading.setVisibility(View.VISIBLE);
            mLoadBitmapTask = new LoadBitmapTask();
            mLoadBitmapTask.execute(uri);
        } else {
            cannotLoadImage();
            done();
        }
    }

    /**
     * Method called on UI thread with loaded bitmap.
     * 當加載完Bitmap之後將Bitmap賦值給mCropView 接著它負責圖像的剪裁工作
     */
    private void doneLoadBitmap(Bitmap bitmap, RectF bounds, int orientation) {
        final View loading = findViewById(R.id.loading);
        loading.setVisibility(View.GONE);
        mOriginalBitmap = bitmap;
        mOriginalBounds = bounds;
        mOriginalRotation = orientation;
        if (bitmap != null && bitmap.getWidth() != 0 && bitmap.getHeight() != 0) {
            RectF imgBounds = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
            mCropView.initialize(bitmap, imgBounds, imgBounds, orientation);
            if (mCropExtras != null) {
                int aspectX = mCropExtras.getAspectX();
                int aspectY = mCropExtras.getAspectY();
                mOutputX = mCropExtras.getOutputX();
                mOutputY = mCropExtras.getOutputY();
                if (mOutputX > 0 && mOutputY > 0) {
                    mCropView.applyAspect(mOutputX, mOutputY);

                }
                float spotX = mCropExtras.getSpotlightX();
                float spotY = mCropExtras.getSpotlightY();
                if (spotX > 0 && spotY > 0) {
                    mCropView.setWallpaperSpotlight(spotX, spotY);
                }
                if (aspectX > 0 && aspectY > 0) {
                    mCropView.applyAspect(aspectX, aspectY);
                }
            }
            enableSave(true);
        } else {
            Log.w(LOGTAG, "could not load image for cropping");
            cannotLoadImage();
            setResult(RESULT_CANCELED, new Intent());
            done();
        }
    }

    /**
     * Display toast for image loading failure.
     * 無法加載圖片
     */
    private void cannotLoadImage() {
        CharSequence text = getString(R.string.cannot_load_image);
        Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
        toast.show();
    }

    /**
     * AsyncTask for loading a bitmap into memory.
     * 異步任務加載bitmap到內存
     * @see #startLoadBitmap(Uri)
     * @see #doneLoadBitmap(Bitmap)
     */
    private class LoadBitmapTask extends AsyncTask {
        int mBitmapSize;
        Context mContext;
        Rect mOriginalBounds;
        int mOrientation;

        public LoadBitmapTask() {
            mBitmapSize = getScreenImageSize();
            mContext = getApplicationContext();
            mOriginalBounds = new Rect();
            mOrientation = 0;
        }

        @Override
        protected Bitmap doInBackground(Uri... params) {
            Uri uri = params[0];
            Bitmap bmap = ImageLoader.loadConstrainedBitmap(uri, mContext, mBitmapSize,
                    mOriginalBounds, false);
            mOrientation = ImageLoader.getMetadataRotation(mContext, uri);
            return bmap;
        }

        @Override
        protected void onPostExecute(Bitmap result) {
            doneLoadBitmap(result, new RectF(mOriginalBounds), mOrientation);
        }
    }

    /**
     * 
     */
    protected void startFinishOutput() {
        if (finalIOGuard) {
            return;
        } else {
            finalIOGuard = true;
        }
        enableSave(false);
        Uri destinationUri = null;
        int flags = 0;
        if (mOriginalBitmap != null && mCropExtras != null) {
            if (mCropExtras.getExtraOutput() != null) {
                destinationUri = mCropExtras.getExtraOutput();
                if (destinationUri != null) {
                    flags |= DO_EXTRA_OUTPUT;
                }
            }
            if (mCropExtras.getSetAsWallpaper()) {
                flags |= DO_SET_WALLPAPER;
            }
            if (mCropExtras.getReturnData()) {
                flags |= DO_RETURN_DATA;
            }
        }
        if (flags == 0) {
            destinationUri = SaveImage.makeAndInsertUri(this, mSourceUri);
            if (destinationUri != null) {
                flags |= DO_EXTRA_OUTPUT;
            }
        }
        if ((flags & FLAG_CHECK) != 0 && mOriginalBitmap != null) {
            RectF photo = new RectF(0, 0, mOriginalBitmap.getWidth(), mOriginalBitmap.getHeight());
            RectF crop = getBitmapCrop(photo);
            startBitmapIO(flags, mOriginalBitmap, mSourceUri, destinationUri, crop,
                    photo, mOriginalBounds,
                    (mCropExtras == null) ? null : mCropExtras.getOutputFormat(), mOriginalRotation);
            return;
        }
        setResult(RESULT_CANCELED, new Intent());
        done();
        return;
    }

    /**
     * @category 開始BitmapIOTask任務
     * @param flags
     * @param currentBitmap
     * @param sourceUri
     * @param destUri
     * @param cropBounds
     * @param photoBounds
     * @param currentBitmapBounds
     * @param format
     * @param rotation
     */
    private void startBitmapIO(int flags, Bitmap currentBitmap, Uri sourceUri, Uri destUri,
            RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format,
            int rotation) {
        if (cropBounds == null || photoBounds == null || currentBitmap == null
                || currentBitmap.getWidth() == 0 || currentBitmap.getHeight() == 0
                || cropBounds.width() == 0 || cropBounds.height() == 0 || photoBounds.width() == 0
                || photoBounds.height() == 0) {
            return; // fail fast
        }
        if ((flags & FLAG_CHECK) == 0) {
            return; // no output options
        }
        if ((flags & DO_SET_WALLPAPER) != 0) {
            Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
        }

        final View loading = findViewById(R.id.loading);
        loading.setVisibility(View.VISIBLE);
        BitmapIOTask ioTask = new BitmapIOTask(sourceUri, destUri, format, flags, cropBounds,
                photoBounds, currentBitmapBounds, rotation, mOutputX, mOutputY);
        ioTask.execute(currentBitmap);
    }

    /**
     * 完成BitmapIO任務
     */
    private void doneBitmapIO(boolean success, Intent intent) {
        final View loading = findViewById(R.id.loading);
        loading.setVisibility(View.GONE);
        if (success) {
            setResult(RESULT_OK, intent);
        } else {
        	
            setResult(RESULT_CANCELED, intent);
        }
        done();
    }

    /**
     * 對傳遞進來的Bimap進行處理,然後設置成壁紙
     */
    private class BitmapIOTask extends AsyncTask {

        private final WallpaperManager mWPManager;
        InputStream mInStream = null;
        OutputStream mOutStream = null;
        String mOutputFormat = null;
        Uri mOutUri = null;
        Uri mInUri = null;
        int mFlags = 0;
        RectF mCrop = null;
        RectF mPhoto = null;
        RectF mOrig = null;
        Intent mResultIntent = null;
        int mRotation = 0;

        // Helper to setup input stream
        private void regenerateInputStream() {
            if (mInUri == null) {
                Log.w(LOGTAG, "cannot read original file, no input URI given");
            } else {
                Utils.closeSilently(mInStream);
                try {
                    mInStream = getContentResolver().openInputStream(mInUri);
                } catch (FileNotFoundException e) {
                    Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
                }
            }
        }

        public BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags,
                RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation,
                int outputX, int outputY) {
            mOutputFormat = outputFormat;
            mOutStream = null;
            mOutUri = destUri;
            mInUri = sourceUri;
            mFlags = flags;
            mCrop = cropBounds;
            mPhoto = photoBounds;
            mOrig = originalBitmapBounds;
            mWPManager = WallpaperManager.getInstance(getApplicationContext());
            mResultIntent = new Intent();
            mRotation = (rotation < 0) ? -rotation : rotation;
            mRotation %= 360;
            mRotation = 90 * (int) (mRotation / 90);  // now mRotation is a multiple of 90
            mOutputX = outputX;
            mOutputY = outputY;

            if ((flags & DO_EXTRA_OUTPUT) != 0) {
                if (mOutUri == null) {
                    Log.w(LOGTAG, "cannot write file, no output URI given");
                } else {
                    try {
                        mOutStream = getContentResolver().openOutputStream(mOutUri);
                    } catch (FileNotFoundException e) {
                        Log.w(LOGTAG, "cannot write file: " + mOutUri.toString(), e);
                    }
                }
            }

            if ((flags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0) {
                regenerateInputStream();
            }
        }

        @Override
        protected Boolean doInBackground(Bitmap... params) {
            boolean failure = false;
            Bitmap img = params[0];

            // Set extra for crop bounds
            if (mCrop != null && mPhoto != null && mOrig != null) {
                RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
                Matrix m = new Matrix();
                m.setRotate(mRotation);
                m.mapRect(trueCrop);
                if (trueCrop != null) {
                    Rect rounded = new Rect();
                    trueCrop.roundOut(rounded);
                    mResultIntent.putExtra(CropExtras.KEY_CROPPED_RECT, rounded);
                }
            }
            
            // Find the small cropped bitmap that is returned in the intent
            if ((mFlags & DO_RETURN_DATA) != 0) {
                assert (img != null);
                Bitmap ret = getCroppedImage(img, mCrop, mPhoto);
                if (ret != null) {
                    ret = getDownsampledBitmap(ret, MAX_BMAP_IN_INTENT);
                }
                if (ret == null) {
                    Log.w(LOGTAG, "could not downsample bitmap to return in data");
                    failure = true;
                } else {
                    if (mRotation > 0) {
                        Matrix m = new Matrix();
                        m.setRotate(mRotation);
                        Bitmap tmp = Bitmap.createBitmap(ret, 0, 0, ret.getWidth(),
                                ret.getHeight(), m, true);
                        if (tmp != null) {
                            ret = tmp;
                        }
                    }
                    mResultIntent.putExtra(CropExtras.KEY_DATA, ret);
                }
            }

            // Do the large cropped bitmap and/or set the wallpaper
            if ((mFlags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0 && mInStream != null) {
                // Find crop bounds (scaled to original image size)
                RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
                if (trueCrop == null) {
                    Log.w(LOGTAG, "cannot find crop for full size image");
                    failure = true;
                    return false;
                }
                Rect roundedTrueCrop = new Rect();
                trueCrop.roundOut(roundedTrueCrop);

                if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
                    Log.w(LOGTAG, "crop has bad values for full size image");
                    failure = true;
                    return false;
                }

                // Attempt to open a region decoder
                BitmapRegionDecoder decoder = null;
                try {
                    decoder = BitmapRegionDecoder.newInstance(mInStream, true);
                } catch (IOException e) {
                    Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
                }
                
                Bitmap crop = null;
                if (decoder != null) {
                    // Do region decoding to get crop bitmap
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inMutable = true;
                    crop = decoder.decodeRegion(roundedTrueCrop, options);
                    decoder.recycle();
                }

                if (crop == null) {
                    // BitmapRegionDecoder has failed, try to crop in-memory
                    regenerateInputStream();
                    Bitmap fullSize = null;
                    if (mInStream != null) {
                        fullSize = BitmapFactory.decodeStream(mInStream);
                    }
                    if (fullSize != null) {
                        crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
                                roundedTrueCrop.top, roundedTrueCrop.width(),
                                roundedTrueCrop.height());
                    }
                }
                
                if (crop == null) {
                    Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
                    failure = true;
                    return false;
                }
                if (mOutputX > 0 && mOutputY > 0) {
                    Matrix m = new Matrix();
                    RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
                    if (mRotation > 0) {
                        m.setRotate(mRotation);
                        m.mapRect(cropRect);
                    }
                    RectF returnRect = new RectF(0, 0, mOutputX, mOutputY);
                    m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
                    m.preRotate(mRotation);
                    Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
                            (int) returnRect.height(), Bitmap.Config.ARGB_8888);
                    if (tmp != null) {
                        Canvas c = new Canvas(tmp);
                        c.drawBitmap(crop, m, new Paint());
                        crop = tmp;
                    }
                } else if (mRotation > 0) {
                    Matrix m = new Matrix();
                    m.setRotate(mRotation);
                    Bitmap tmp = Bitmap.createBitmap(crop, 0, 0, crop.getWidth(),
                            crop.getHeight(), m, true);
                    if (tmp != null) {
                        crop = tmp;
                    }
                }
                // Get output compression format
                CompressFormat cf =
                        convertExtensionToCompressFormat(getFileExtension(mOutputFormat));

                // If we only need to output to a URI, compress straight to file
                if (mFlags == DO_EXTRA_OUTPUT) {
                    if (mOutStream == null
                            || !crop.compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream)) {
                        Log.w(LOGTAG, "failed to compress bitmap to file: " + mOutUri.toString());
                        failure = true;
                    } else {
                        mResultIntent.setData(mOutUri);
                    }
                } else {
                    // Compress to byte array
                    ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
                    if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {

                        // If we need to output to a Uri, write compressed
                        // bitmap out
                        if ((mFlags & DO_EXTRA_OUTPUT) != 0) {
                            if (mOutStream == null) {
                                Log.w(LOGTAG,
                                        "failed to compress bitmap to file: " + mOutUri.toString());
                                failure = true;
                            } else {
                                try {
                                    mOutStream.write(tmpOut.toByteArray());
                                    mResultIntent.setData(mOutUri);
                                } catch (IOException e) {
                                    Log.w(LOGTAG,
                                            "failed to compress bitmap to file: "
                                                    + mOutUri.toString(), e);
                                    failure = true;
                                }
                            }
                        }

                        // If we need to set to the wallpaper, set it
                        if ((mFlags & DO_SET_WALLPAPER) != 0 && mWPManager != null) {
                            if (mWPManager == null) {
                                Log.w(LOGTAG, "no wallpaper manager");
                                failure = true;
                            } else {
                                try {
                                    mWPManager.setStream(new ByteArrayInputStream(tmpOut
                                            .toByteArray()));
                                } catch (IOException e) {
                                    Log.w(LOGTAG, "cannot write stream to wallpaper", e);
                                    failure = true;
                                }
                            }
                        }
                    } else {
                        Log.w(LOGTAG, "cannot compress bitmap");
                        failure = true;
                    }
                }
            }
            return !failure; // True if any of the operations failed
        }

        @Override
        protected void onPostExecute(Boolean result) {
            Utils.closeSilently(mOutStream);
            Utils.closeSilently(mInStream);
            doneBitmapIO(result.booleanValue(), mResultIntent);
        }

    }

    private void done() {
        finish();
    }

    /**
     * @category 返回CroppedImage
     * @param image
     * @param cropBounds
     * @param photoBounds
     * @return
     */
    protected static Bitmap getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds) {
        RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
        RectF crop = CropMath.getScaledCropBounds(cropBounds, photoBounds, imageBounds);
        if (crop == null) {
            return null;
        }
        Rect intCrop = new Rect();
        crop.roundOut(intCrop);
        return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
                intCrop.height());
    }

    protected static Bitmap getDownsampledBitmap(Bitmap image, int max_size) {
        if (image == null || image.getWidth() == 0 || image.getHeight() == 0 || max_size < 16) {
            throw new IllegalArgumentException("Bad argument to getDownsampledBitmap()");
        }
        int shifts = 0;
        int size = CropMath.getBitmapSize(image);
        while (size > max_size) {
            shifts++;
            size /= 4;
        }
        Bitmap ret = Bitmap.createScaledBitmap(image, image.getWidth() >> shifts,
                image.getHeight() >> shifts, true);
        if (ret == null) {
            return null;
        }
        // Handle edge case for rounding.
        if (CropMath.getBitmapSize(ret) > max_size) {
            return Bitmap.createScaledBitmap(ret, ret.getWidth() >> 1, ret.getHeight() >> 1, true);
        }
        return ret;
    }

    /**
     * Gets the crop extras from the intent, or null if none exist.
     */
    protected static CropExtras getExtrasFromIntent(Intent intent) {
        Bundle extras = intent.getExtras();
        if (extras != null) {
            return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
                    extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
                    extras.getBoolean(CropExtras.KEY_SCALE, true) &&
                            extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
                    extras.getInt(CropExtras.KEY_ASPECT_X, 0),
                    extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
                    extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
                    extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
                    (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
                    extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
                    extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
                    extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
                    extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
        }
        return null;
    }

    protected static CompressFormat convertExtensionToCompressFormat(String extension) {
        return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
    }

    protected static String getFileExtension(String requestFormat) {
        String outputFormat = (requestFormat == null)
                ? "jpg"
                : requestFormat;
        outputFormat = outputFormat.toLowerCase();
        return (outputFormat.equals("png") || outputFormat.equals("gif"))
                ? "png" // We don't support gif compression.
                : "jpg";
    }

    private RectF getBitmapCrop(RectF imageBounds) {
        RectF crop = mCropView.getCrop();
        RectF photo = mCropView.getPhoto();
        if (crop == null || photo == null) {
            Log.w(LOGTAG, "could not get crop");
            return null;
        }
        RectF scaledCrop = CropMath.getScaledCropBounds(crop, photo, imageBounds);
        return scaledCrop;
    }
}
CropActivity的處理邏輯是這樣,首先開啟一個異步任務根據傳遞過來的信息去加載壁紙Bitmap。加載完畢再開啟一個異步任務去處理這個Bitmap,處理完最後設置為壁紙。代碼已經添加了注釋。 設置完畢返回到桌面,會有1到2秒的設置時間,之後壁紙就設置好了。設置好的壁紙是可以隨著桌面滑動的,這麼說不太准確,應該說是Launcher讓壁紙隨著桌面滑動而滑動(parallax effects)。處理邏輯是在Workspace類裡,由於Workspace類代碼較長就不貼代碼了,找相關代碼分析。變量:
     private float mWallpaperScrollRatio = 1.0f;
     private int mOriginalPageSpacing ;

     private final WallpaperManager mWallpaperManager;
     private IBinder mWindowToken;
     private static final float WALLPAPER_SCREENS_SPAN = 2f;


     enum WallpaperVerticalOffset {
           TOP, MIDDLE , BOTTOM
     };

     int mWallpaperWidth;
     int mWallpaperHeight;
     WallpaperOffsetInterpolator mWallpaperOffset;
     boolean mUpdateWallpaperOffsetImmediately = false ;
     private Runnable mDelayedResizeRunnable;
     private Runnable mDelayedSnapToPageRunnable;
     private Point mDisplaySize = new Point();
     private boolean mIsStaticWallpaper ;
     private int mWallpaperTravelWidth ;
     private int mSpringLoadedPageSpacing ;
     private int mCameraDistance ;

     mWallpaperManager = WallpaperManager.getInstance(context);
     mWallpaperOffset = new WallpaperOffsetInterpolator();
     mWallpaperTravelWidth = (int) (mDisplaySize. x * wallpaperTravelToScreenWidthRatio(mDisplaySize.x , mDisplaySize .y ));
     mIsStaticWallpaper = mWallpaperManager.getWallpaperInfo() == null ;
方法和類:
      setWallpaperDimension()
      wallpaperTravelToScreenWidthRatio()
      wallpaperOffsetForCurrentScroll()
      syncWallpaperOffsetWithScroll()
      updateWallpaperOffsetImmediately()
      updateWallpaperOffsets()
      computeWallpaperScrollRatio()
      WallpaperOffsetInterpolator類
可以說是WallpaperOffsetInterpolator負責壁紙滑動的,其他變量和方法為它服務。setWallpaperDimension()方法是設置壁紙的大小,這個方法在Launcher類初始化時候會調用。wallpaperOffsetForCurrentScroll()方法用於計算滑動壁紙需要移動的距離,它在syncWallpaperOffsetWithScroll()方法中調用,syncWallpaperOffsetWithScroll()方法很簡單,如果手機開啟硬件加速就執行WallpaperOffsetInterpolator的setFinalX方法,setFinalX方法再去調用wallpaperOffsetForCurrentScroll()方法。
private void syncWallpaperOffsetWithScroll() {
           final boolean enableWallpaperEffects = isHardwareAccelerated();
           if (enableWallpaperEffects) {
                mWallpaperOffset.setFinalX(wallpaperOffsetForCurrentScroll());
          }
     }
如果我們把enableWallpaperEffects值改為false會怎麼樣呢,沒錯就是固定不動的壁紙了。updateWallpaperOffsets()方法用於在ondraw()方法中去更新視圖(滑動的時候),配合在computeScroll()方法中調用syncWallpaperOffsetWithScroll()方法。
@Override
      public void computeScroll() {
           super .computeScroll();
          syncWallpaperOffsetWithScroll();
     }
關於Launcher設置壁紙的流程就先介紹到這裡,有疏漏的地方還請指正。有功能就會有Bug,那麼一般關於壁紙常見的Bug有哪些呢?下一篇文章會具體介紹,有這樣的經驗的朋友歡迎一切探討、交流。
花絮:https://android.googlesource.com/platform/packages/apps/Gallery2/從這裡下載的源碼不完全,需要從以下http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android-apps/4.4_r1/com/android/gallery3d依賴類庫:https://code.google.com/p/mp4parser/
圖像剪裁開源項目:1、https://github.com/edmodo/cropper(比較完善)2、https://github.com/jdamcd/android-crop3、https://github.com/biokys/cropimage4、https://github.com/lvillani/android-cropimage
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved