Launcher3--壁纸

    在说Launcher上设置壁纸时,首先需要弄清楚的是,壁纸的设置属于系统行为,而不是Launcher的应用特性。在Launcher中,壁纸的设置最终也是通过调用系统壁纸设置接口来完成的,所有,不仅仅是Launcher,很多第三方应用也是可以设置壁纸的。
    Android中,可以使用WallpaperManager这一壁纸管理类来设置壁纸,有如下几种方法,

    我们可以根据壁纸资源的不同,选择合适的方法,其中,最后一个可以用来设置动态壁纸。

    下面就来说说Launcher3中是如何设置壁纸的,我们直接从壁纸设置界面的入口说起,
    /**
     * Event handler for the wallpaper picker button that appears after a long press
     * on the home screen.
     */
    protected void onClickWallpaperPicker(View v) {
        if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
        final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
        pickWallpaper.setComponent(getWallpaperPickerComponent());
        startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.onClickWallpaperPicker(v);
        }
    }
    protected ComponentName getWallpaperPickerComponent() {
        if (mLauncherCallbacks != null) {
            return mLauncherCallbacks.getWallpaperPickerComponent();
        }
        return new ComponentName(getPackageName(), LauncherWallpaperPickerActivity.class.getName());
    }
    很显然, LauncherWallpaperPickerActivity 就是壁纸设置界面了, LauncherWallpaperPickerActivity继承于 WallpaperPickerActivity, WallpaperPickerActivity又继承于 WallpaperCropActivity,这么多继承,看来这个界面还是比较复杂的。从命名来看的话,也是为了区分每个类的处理重点,WallpaperCropActivity用来进行壁纸的裁剪,将图片裁剪到合适的尺寸;WallpaperPickerActivity就是壁纸选择器,选择壁纸并设置;至于LauncherWallpaperPickerActivity,从代码中看到只是重写了父类的两个方法,没什么可分析的,这里我们重点分析WallpaperPickerActivity这个类。
一、壁纸类型对象
    作为内部类,定义在WallpaperPickerActivity类中,
    public static abstract class WallpaperTileInfo {
        protected View mView;
        public Drawable mThumb;

        public void setView(View v) {
            mView = v;
        }
        public void onClick(WallpaperPickerActivity a) {}// 缩略图点击事件
        public void onSave(WallpaperPickerActivity a) {}// 设置壁纸,并做一些保存操作
        public void onDelete(WallpaperPickerActivity a) {}// 删除壁纸
        public boolean isSelectable() { return false; }// 是否可选
        public boolean isNamelessWallpaper() { return false; }// 壁纸是否没有名字
        public void onIndexUpdated(CharSequence label) {// 更新索引
            if (isNamelessWallpaper()) {
                mView.setContentDescription(label);
            }
        }
    }
    壁纸对象的一个抽象类,不直接使用,具体的壁纸继承该类并根据自身特点扩展。壁纸来源有多个途径,如应用内置的壁纸、图库、第三方等,另外设为壁纸的方式也不一定相同,需要对不同来源区分处理,所有就定义了以下几个壁纸类对象,
PickImageInfo--图片选择器,在Activity中添加属性<action android:name="android.intent.action.GET_CONTENT" />,就可以隐式调用到,如图库
UriWallpaperInfo--通过图片的Uri来设置壁纸
FileWallpaperInfo--通过图片文件来设置壁纸
ResourceWallpaperInfo--Launcher3中内置的壁纸资源来设置
DefaultWallpaperInfo--系统默认壁纸,资源在framework中
    这几个类实现其抽象父类中的方法,具体代码实现就不一一细说,后面说到具体方法时会举其中的例子来说明,这里对几个抽象方法已经做了注释。

二、加载壁纸列表
    图1是壁纸设置界面,界面简单,包含了壁纸列表、设置壁纸按钮以及壁纸预览图等。
Launcher3--壁纸_第1张图片
图1
    WallpaperPickerActivity中没有重写onCreate方法,而是通过父类的onCreate的方法调用了重写的init方法,进行布局的加载和初始化。
1、布局
<?xml version="1.0" encoding="utf-8"?><!--
/*
**
** Copyright 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.
*/
-->

<com.android.launcher3.WallpaperRootView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/wallpaper_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.android.launcher3.CropView
        android:id="@+id/cropView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ProgressBar
        android:id="@+id/loading"
        style="@android:style/Widget.Holo.ProgressBar.Large"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="@android:color/transparent"
        android:indeterminate="true"
        android:indeterminateOnly="true"
        android:visibility="invisible" />

    <LinearLayout
        android:id="@+id/wallpaper_strip"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="vertical">

        <View
            android:layout_width="match_parent"
            android:layout_height="2dp"
            android:background="@drawable/tile_shadow_top" />

        <HorizontalScrollView
            android:id="@+id/wallpaper_scroll_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <LinearLayout
                android:id="@+id/master_wallpaper_list"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

                <LinearLayout
                    android:id="@+id/wallpaper_list"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal" />

                <LinearLayout
                    android:id="@+id/live_wallpaper_list"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal" />

                <LinearLayout
                    android:id="@+id/third_party_wallpaper_list"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal" />
            </LinearLayout>
        </HorizontalScrollView>

        <View
            android:layout_width="match_parent"
            android:layout_height="2dp"
            android:background="@drawable/tile_shadow_bottom" />
    </LinearLayout>
</com.android.launcher3.WallpaperRootView>
    WallpaperRootView是根视图,继承RelativeLayout自定义的一个视图,重写了fitSystemWindows方法,
    protected boolean fitSystemWindows(Rect insets) {
        a.setWallpaperStripYOffset(insets.bottom);
        return true;
    }
    这么做的目的是为了让视图内离底部一段距离,否则会出现如图2的情况,跟虚拟键重合,就不大美观了。

Launcher3--壁纸_第2张图片
图2
        setContentView(R.layout.wallpaper_picker);

        mCropView = (CropView) findViewById(R.id.cropView);
        mCropView.setVisibility(View.INVISIBLE);// 默认是不可见的

        mWallpaperStrip = findViewById(R.id.wallpaper_strip);
1)CropView--裁剪视图,用于壁纸的裁剪、预览,还有手势操作(两个手指缩放)。
2)进度条--加载该界面时的进度条。
3)壁纸列表--LinearLayout布局块,其中的子视图HorizontalScrollView是一个横向的滑动视图,就是我们的壁纸列表,也是根据壁纸类型的分了多个布局块,分别加载
    好像还少了ActionBar,这个是在代码中动态添加的,下面会说到。

2、接口回调和监听事件
        mCropView.setTouchCallback(new CropView.TouchCallback() {
            ViewPropertyAnimator mAnim;
            @Override
            public void onTouchDown() {
                if (mAnim != null) {
                    mAnim.cancel();
                }
                if (mWallpaperStrip.getAlpha() == 1f) {
                    mIgnoreNextTap = true;
                }
                mAnim = mWallpaperStrip.animate();
                mAnim.alpha(0f)
                    .setDuration(150)
                    .withEndAction(new Runnable() {
                        public void run() {
                            mWallpaperStrip.setVisibility(View.INVISIBLE);
                        }
                    });
                mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
                mAnim.start();
            }
            @Override
            public void onTouchUp() {
                mIgnoreNextTap = false;
            }
            @Override
            public void onTap() {
                boolean ignoreTap = mIgnoreNextTap;
                mIgnoreNextTap = false;
                if (!ignoreTap) {
                    if (mAnim != null) {
                        mAnim.cancel();
                    }
                    mWallpaperStrip.setVisibility(View.VISIBLE);
                    mAnim = mWallpaperStrip.animate();
                    mAnim.alpha(1f)
                         .setDuration(150)
                         .setInterpolator(new DecelerateInterpolator(0.75f));
                    mAnim.start();
                }
            }
        });
    CropView的touch回调处理,这里只做了一些动画效果,具体裁剪的操作还是在CropView中实现的,这里就不详细说明了。
        mThumbnailOnClickListener = new OnClickListener() {
            public void onClick(View v) {
                if (mActionMode != null) {
                    // When CAB is up, clicking toggles the item instead
                    if (v.isLongClickable()) {
                        mLongClickListener.onLongClick(v);
                    }
                    return;
                }
                mSetWallpaperButton.setEnabled(true);
                WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
                if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
                    selectTile(v);
                }
                info.onClick(WallpaperPickerActivity.this);// 缩略图点击事件
            }
        };
    缩略图点击事件,如果处于ActionMode(长按事件),处理长按事件,否则回调该壁纸所实现的onClick方法,启用mSetWallpaperButton,该控件定义在其父类WallpaperCropActivity中,
        // Action bar
        // Show the custom action bar view
        final ActionBar actionBar = getActionBar();
        actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
        actionBar.getCustomView().setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        boolean finishActivityWhenDone = true;
                        cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
                    }
                });
        mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);

        mLongClickListener = new View.OnLongClickListener() {
            // Called when the user long-clicks on someView
            public boolean onLongClick(View view) {
                CheckableFrameLayout c = (CheckableFrameLayout) view;
                c.toggle();
                if (mActionMode != null) {
                    mActionMode.invalidate();
                } else {
                    // Start the CAB using the ActionMode.Callback defined below
                    mActionMode = startActionMode(mActionModeCallback);
                    int childCount = mWallpapersView.getChildCount();
                    for (int i = 0; i < childCount; i++) {
                        mWallpapersView.getChildAt(i).setSelected(false);
                    }
                }
                return true;
            }
        };
 定义了缩略图长按事件,并不是所有的壁纸类型都设置了长按事件,下面会讲到。

3、获取壁纸资源,将缩略图加载到横向scrollview

    1)添加Launcher3中内置的壁纸资源和系统默认壁纸
        // Populate the built-in wallpapers
        // 填充内置壁纸,资源文件配置的壁纸和系统默认壁纸
        ArrayList<WallpaperTileInfo> wallpapers = findBundledWallpapers();
        mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
        SimpleWallpapersAdapter ia = new SimpleWallpapersAdapter(this, wallpapers);
        populateWallpapersFromAdapter(mWallpapersView, ia, false);
    通过 findBundledWallpapers 来查找壁纸,
    private ArrayList<WallpaperTileInfo> findBundledWallpapers() {
        final PackageManager pm = getPackageManager();
        final ArrayList<WallpaperTileInfo> bundled = new ArrayList<WallpaperTileInfo>(24);

        Partner partner = Partner.get(pm);
        if (partner != null) {
            final Resources partnerRes = partner.getResources();
            final int resId = partnerRes.getIdentifier(Partner.RES_WALLPAPERS, "array",
                    partner.getPackageName());
            if (resId != 0) {
                addWallpapers(bundled, partnerRes, partner.getPackageName(), resId);
            }

            // Add system wallpapers
            File systemDir = partner.getWallpaperDirectory();
            if (systemDir != null && systemDir.isDirectory()) {
                for (File file : systemDir.listFiles()) {
                    if (!file.isFile()) {
                        continue;
                    }
                    String name = file.getName();
                    int dotPos = name.lastIndexOf('.');
                    String extension = "";
                    if (dotPos >= -1) {
                        extension = name.substring(dotPos);
                        name = name.substring(0, dotPos);
                    }

                    if (name.endsWith("_small")) {
                        // it is a thumbnail
                        continue;
                    }

                    File thumbnail = new File(systemDir, name + "_small" + extension);
                    Bitmap thumb = BitmapFactory.decodeFile(thumbnail.getAbsolutePath());
                    if (thumb != null) {
                        bundled.add(new FileWallpaperInfo(file, new BitmapDrawable(thumb)));
                    }
                }
            }
        }

        // 添加Launcher中配置的壁纸
        Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
        if (r != null) {
            try {
                Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first);
                addWallpapers(bundled, wallpaperRes, r.first.packageName, r.second);
            } catch (PackageManager.NameNotFoundException e) {
            }
        }

        // 创建一个空的实体,用于放置默认壁纸
        if (partner == null || !partner.hideDefaultWallpaper()) {
            // Add an entry for the default wallpaper (stored in system resources)
            WallpaperTileInfo defaultWallpaperInfo =
                    (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
                    ? getPreKKDefaultWallpaperInfo()
                    : getDefaultWallpaper();
            if (defaultWallpaperInfo != null) {
                bundled.add(0, defaultWallpaperInfo);
            }
        }
        return bundled;
    }
    加载系统中有监听特定广播的应用中的资源,这个广播是"com.android.launcher3.action.PARTNER_CUSTOMIZATION"
    加载Launcher3中配置的壁纸,这些壁纸放在drawable-xxx目录下,并在wallpapers.xml中配置(必须有原图和缩略图)
<resources>
    <string-array name="wallpapers" translatable="false">
        <!-- This special drawable references the platform's private
             default_wallpaper resource so the user can always choose it. -->
        <item>zzz_wallpaper</item>
        <item>zzz_wallpaper_small</item>
        <!-- If you want additional bitmap drawable resources to appear in the
             wallpaper picker, add them to this list. For each foo.jpg be sure
             to include a foo_small.jpg to be used as a thumbnail in the
             scrolling gallery widget. -->
    </string-array>
</resources>
    加载默认壁纸,默认壁纸放在framework资源目录下
     这样就获取到壁纸列表,定义适配器,通过 populateWallpapersFromAdapter 方法将其显示,
    private void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
                                               boolean addLongPressHandler) {
        for (int i = 0; i < adapter.getCount(); i++) {
            FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent);
            parent.addView(thumbnail, i);
            WallpaperTileInfo info = (WallpaperTileInfo) adapter.getItem(i);
            thumbnail.setTag(info);
            info.setView(thumbnail);
            if (addLongPressHandler) {// 是否添加长按事件,只对数据库中保存的壁纸处理
                addLongPressHandler(thumbnail);
            }
            thumbnail.setOnClickListener(mThumbnailOnClickListener);
        }
    }
    这个方法比较好理解,需要注意的是第三个参数,这个布尔值用来确定该类型壁纸是否添加长按事件,这里是false,不添加;根据后面的分析来看,也只有保存在数据库中的壁纸添加该操作,这也好理解,因为其他几种类型都不是用户自己定义的,不允许删除壁纸,长按操作就是用来删除该壁纸的。
    2)添加保存在数据库中的壁纸
        // Populate the saved wallpapers
        // 填充保存在数据库中的壁纸
        mSavedImages = new SavedWallpaperImages(this);
        mSavedImages.loadThumbnailsAndImageIdList();
        populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true);
    3)添加动态壁纸
        // Populate the live wallpapers
        // 填充动态壁纸
        final LinearLayout liveWallpapersView =
                (LinearLayout) findViewById(R.id.live_wallpaper_list);
        final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(this);
        a.registerDataSetObserver(new DataSetObserver() {
            public void onChanged() {
                liveWallpapersView.removeAllViews();
                populateWallpapersFromAdapter(liveWallpapersView, a, false);
                initializeScrollForRtl();
                updateTileIndices();
            }
        });
    在Android中,除了可以显示静态壁纸外,也可以使用动态壁纸。当然,跟普通的壁纸不同的是,它是已apk的形式安装到手机中的(至于怎么制作一个动态壁纸的apk,不是我们这边所讲的,就不阐述了),加载动态壁纸就是要查找系统中已安装的动态壁纸应用。
    动态壁纸也定义了一个适配器类LiveWallpaperListAdapter,定义动态壁纸对象,查找动态壁纸应用等。
    public LiveWallpaperListAdapter(Context context) {
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mPackageManager = context.getPackageManager();

        List<ResolveInfo> list = mPackageManager.queryIntentServices(
                new Intent(WallpaperService.SERVICE_INTERFACE),
                PackageManager.GET_META_DATA);

        mWallpapers = new ArrayList<LiveWallpaperTile>();

        new LiveWallpaperEnumerator(context).execute(list);
    }
    这是构造方法,查询action为 "android.service.wallpaper.WallpaperService" 的service,这是动态壁纸应用中必须配置的,如果我们自己想做一个动态壁纸也是要添加这个action的。
    public static class LiveWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
        private Drawable mThumbnail;
        private WallpaperInfo mInfo;
        public LiveWallpaperTile(Drawable thumbnail, WallpaperInfo info, Intent intent) {
            mThumbnail = thumbnail;
            mInfo = info;
        }
        @Override
        public void onClick(WallpaperPickerActivity a) {
            Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
            preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
                    mInfo.getComponent());
            a.onLiveWallpaperPickerLaunch(mInfo);
            a.startActivityForResultSafely(preview, WallpaperPickerActivity.PICK_LIVE_WALLPAPER);
        }
    }
      WallpaperTileInfo的子类,然后异步加载信息。
            for (ResolveInfo resolveInfo : list) {
                WallpaperInfo info = null;
                try {
                    info = new WallpaperInfo(mContext, resolveInfo);
                } catch (XmlPullParserException e) {
                    Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
                    continue;
                } catch (IOException e) {
                    Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
                    continue;
                }

                // 获取动态壁纸信息
                Drawable thumb = info.loadThumbnail(packageManager);
                Intent launchIntent = new Intent(WallpaperService.SERVICE_INTERFACE);
                launchIntent.setClassName(info.getPackageName(), info.getServiceName());
                LiveWallpaperTile wallpaper = new LiveWallpaperTile(thumb, info, launchIntent);
                publishProgress(wallpaper);
            }
    4)第三方壁纸
        // Populate the third-party wallpaper pickers
        // 填充第三方壁纸选择器
        final LinearLayout thirdPartyWallpapersView =
                (LinearLayout) findViewById(R.id.third_party_wallpaper_list);
        final ThirdPartyWallpaperPickerListAdapter ta =
                new ThirdPartyWallpaperPickerListAdapter(this);
        populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false);
    加载第三方壁纸选择器,这个还是很友好的,这样手机中如果装有其他的第三方壁纸设置的应用,也可以在此处显示出来。查询是在ThirdPartyWallpaperPickerListAdapter适配器类中进行的,这个适配器跟刚才说的动态壁纸适配器类类似。
    定义了第三方壁纸对象ThirdPartyWallpaperTile,
    public static class ThirdPartyWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {

        private ResolveInfo mResolveInfo;
        public ThirdPartyWallpaperTile(ResolveInfo resolveInfo) {
            mResolveInfo = resolveInfo;
        }

        @Override
        public void onClick(WallpaperPickerActivity a) {
            final ComponentName itemComponentName = new ComponentName(
                    mResolveInfo.activityInfo.packageName, mResolveInfo.activityInfo.name);
            Intent launchIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
            launchIntent.setComponent(itemComponentName);
            a.startActivityForResultSafely(launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);// 启动第三方壁纸选择器
        }
    }
    在构造方法中查询第三方壁纸应用,
    public ThirdPartyWallpaperPickerListAdapter(Context context) {
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mPackageManager = context.getPackageManager();
        mIconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize);
        final PackageManager pm = mPackageManager;

        final Intent pickWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
        final List<ResolveInfo> apps = pm.queryIntentActivities(pickWallpaperIntent, 0);

        // Get list of image picker intents
        Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT);
        pickImageIntent.setType("image/*");
        final List<ResolveInfo> imagePickerActivities =
                pm.queryIntentActivities(pickImageIntent, 0);
        final ComponentName[] imageActivities = new ComponentName[imagePickerActivities.size()];
        for (int i = 0; i < imagePickerActivities.size(); i++) {
            ActivityInfo activityInfo = imagePickerActivities.get(i).activityInfo;
            imageActivities[i] = new ComponentName(activityInfo.packageName, activityInfo.name);
        }

        outerLoop:
        for (ResolveInfo info : apps) {
            final ComponentName itemComponentName =
                    new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
            final String itemPackageName = itemComponentName.getPackageName();
            // Exclude anything from our own package, and the old Launcher,
            // and live wallpaper picker
            if (itemPackageName.equals(context.getPackageName()) ||
                    itemPackageName.equals("com.android.launcher") ||
                    itemPackageName.equals("com.android.wallpaper.livepicker")) {
                continue;
            }
            // Exclude any package that already responds to the image picker intent
            for (ResolveInfo imagePickerActivityInfo : imagePickerActivities) {
                if (itemPackageName.equals(
                        imagePickerActivityInfo.activityInfo.packageName)) {
                    continue outerLoop;
                }
            }
            mThirdPartyWallpaperPickers.add(new ThirdPartyWallpaperTile(info));
        }
    }
    根据 "android.intent.action.SET_WALLPAPER" 来查找的,然后做一些过滤,添加到列表中。
    5)添加图库
        // Add a tile for the Gallery
        // 列表开头放置图库选择器
        LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
        FrameLayout pickImageTile = (FrameLayout) getLayoutInflater().
                inflate(R.layout.wallpaper_picker_image_picker_item, masterWallpaperList, false);
        setWallpaperItemPaddingToZero(pickImageTile);
        masterWallpaperList.addView(pickImageTile, 0);

        // Make its background the last photo taken on external storage
        Bitmap lastPhoto = getThumbnailOfLastPhoto();
        if (lastPhoto != null) {
            ImageView galleryThumbnailBg =
                    (ImageView) pickImageTile.findViewById(R.id.wallpaper_image);
            galleryThumbnailBg.setImageBitmap(getThumbnailOfLastPhoto());
            int colorOverlay = getResources().getColor(R.color.wallpaper_picker_translucent_gray);
            galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);

        }

        PickImageInfo pickImageInfo = new PickImageInfo();
        pickImageTile.setTag(pickImageInfo);
        pickImageInfo.setView(pickImageTile);
        pickImageTile.setOnClickListener(mThumbnailOnClickListener);
    在列表开头添加图库入口,这样用户就可以选择任一图片了。
    
    其他的初始化设置就不一一赘述了。

三、壁纸预览和设置
    之前说到不同类型的壁纸对象时,会重写父类的方法,实现具体的功能,这里我们已ResourceWallpaperInfo为例,来说明壁纸的预览和设置的。
        @Override
        public void onClick(WallpaperPickerActivity a) {
            Log.d("dingfeng","ResourceWallpaperInfo onClick...");
            BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
                    new BitmapRegionTileSource.ResourceBitmapSource(
                            mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
            bitmapSource.loadInBackground();
            BitmapRegionTileSource source = new BitmapRegionTileSource(a, bitmapSource);
            CropView v = a.getCropView();
            v.setTileSource(source, null);
            Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize(
                    a.getResources(), a.getWindowManager());
            RectF crop = WallpaperCropActivity.getMaxCropRect(
                    source.getImageWidth(), source.getImageHeight(),
                    wallpaperSize.x, wallpaperSize.y, false);
            v.setScale(wallpaperSize.x / crop.width());
            v.setTouchEnabled(false);
            a.setSystemWallpaperVisiblity(false);
        }
        @Override
        public void onSave(WallpaperPickerActivity a) {
            Log.d("dingfeng","ResourceWallpaperInfo onSave...");
            boolean finishActivityWhenDone = true;
            a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);
        }
        @Override
        public boolean isSelectable() {
            return true;
        }
        @Override
        public boolean isNamelessWallpaper() {
            return true;
        }
    实现了四个方法,后面两个返回bool值得含义之前已经说过,我们不细说。先看onClick,这个方法在点击缩略图列表是触发,看看它究竟做了什么。
    这面用到了BitmapRegionTileSource及其内部类对象,这些类定义在src\main\java\com\android\photos\目录下,自定义了图片对象,实现了滚动、缩放等功能,这里就不展开了,可以自己查看代码 。
Launcher3--壁纸_第3张图片
     生成BitmapRegionTileSource对象后,设置到CropView上,然后做合适的缩放,再将系统壁纸设为不可见,这样就可以达到壁纸预览的目的。
     再看onSave方法,这个方法在点击ActionBar时调用,该方法中调用WallpaperCropActivity的cropImageAndSetWallpaper来裁剪和设置壁纸,
    protected void cropImageAndSetWallpaper(
            Resources res, int resId, final boolean finishActivityWhenDone) {
        // crop this image and scale it down to the default wallpaper size for
        // this device
        int rotation = getRotationFromExif(res, resId);
        Point inSize = mCropView.getSourceDimensions();
        Point outSize = getDefaultWallpaperSize(getResources(), getWindowManager());
        RectF crop = getMaxCropRect(inSize.x, inSize.y, outSize.x, outSize.y, false);
        Runnable onEndCrop = new Runnable() {
            public void run() {
                // Passing 0, 0 will cause launcher to revert to using the
                // default wallpaper size
                updateWallpaperDimensions(0, 0);
                if (finishActivityWhenDone) {
                    setResult(Activity.RESULT_OK);
                    finish();
                }
            }
        };
        BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,
                crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
        cropTask.execute();
    }
    设置裁剪大小,将其作为参数传递给异步任务执行,
        @Override
        protected Boolean doInBackground(Void... params) {
            return cropBitmap();
        }
    最终就是 cropBitmap 方法来做最后的裁剪和壁纸设置操作。
    其他几种类型的壁纸也是类似的,根据壁纸来源做出相应的操作,比如第三方壁纸时,点击缩略图就是打开第三方应用;如果是图库,就打开图库,总之都是在这几个重写方法中实现的。如果以后有什么不同于目前几种类型的,也可以依此来扩展。
    



你可能感兴趣的:(android,Launcher3)