经过整整一周的学习,总算实现了android中源生背景的修改。效果如下:
分别是修改前的原始界面、选择背景设置界面以及最后的效果图。
之前一直想用ScrollView来实现这个效果,后来发现用Gallery会简单很多。虽然Gallery被谷歌抛弃了,不过在4.0源码中还是有着它的大量身影。
设计思路很简单:布局->事件->处理->保存状态。布局文件参考的源码Launcher中的wallpaperchooer。事件处理则是使用StartActivityForResult来获得图片的ResID。处理是简单的获得背景layout后用获得的ResID来进行填充。保存状态这一步暂时还没有来的及做,所以,一旦手机重启,背景界面就会重置。。。
布局文件如下(我弄懂的地方已注释):
public class BackgroundChooserFragment extends Fragment implements AdapterView.OnItemSelectedListener, AdapterView.OnItemClickListener{ private ArrayList<Integer> mThumbs; //下方的小图 private ArrayList<Integer> mImages; //用做背景的大图 private Bitmap mBitmap = null; //绘图时会用到bitmap private WallpaperLoader mLoader; //加载图片 private WallpaperDrawable mWallpaperDrawable = new WallpaperDrawable(); //图片的绘制 private Intent intent; //用来向Launcher回传ResID @Override public void onCreate(Bundle savedInstanceState) { //系统创建Fragments 时调用, //可做执行初始化工作或者当程 //序被暂停或停止时用来恢复状态, //跟Activity 中的onCreate相当。 super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // 用于首次绘制用户界面的回调方法,必须返回要创 //建的Fragments 视图UI。假如你不希望提供 //Fragments 用户界面则可以返回NULL。 // TODO Auto-generated method stub findWallpapers(); View view = inflater.inflate(R.layout.background_dialogchooser, container, false); //在实际开发中LayoutInflater这个类还是非常有用的,它的作用类似于findViewById()。不同点是 //LayoutInflater是用来找res/layout/下的xml布局文件,并且实例化;而findViewById()是找xml //布局文件下的具体widget控件(如Button、TextView等)。 view.setBackgroundDrawable(mWallpaperDrawable); //铺设背景图片 final Gallery gallery = (Gallery) view.findViewById(R.id.background_gallery); //获得Gallery gallery.setCallbackDuringFling(false); gallery.setOnItemSelectedListener(this); gallery.setAdapter(new ImageAdapter(getActivity())); //为Gallery添加适配器,左右的拖动就是在这里实现的 View setButton = view.findViewById(R.id.background_set); setButton.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { selectWallpaper(gallery.getSelectedItemPosition()); } }); return view; } private void selectWallpaper(int position) { Activity activity = getActivity(); //这是个fragment,所以必需要先获得父Activity才能使用Intent intent = new Intent(activity, Launcher.class); intent.putExtra("background", mImages.get(position)); activity.setResult(Activity.RESULT_OK,intent); //数据的回传在这里 activity.finish(); //调用finish时会马上执行intent } private void findWallpapers() { //本方法用来加载图片资源 mThumbs = new ArrayList<Integer>(24); //最多24张图片 mImages = new ArrayList<Integer>(24); final Resources resources = getResources(); final String packageName = resources.getResourcePackageName(R.array.wallpapers); addWallpapers(resources, packageName, R.array.wallpapers); addWallpapers(resources, packageName, R.array.extra_wallpapers); //同一个包下的两个数组文件 } private void addWallpapers(Resources resources, String packageName, int list) { final String[] extras = resources.getStringArray(list); for (String extra : extras) { //使用getIdentifier()获取资源Id,参数(ID名,文件夹名,包名) int res = resources.getIdentifier(extra, "drawable", packageName); if (res != 0) { final int thumbRes = resources.getIdentifier(extra + "_small", "drawable", packageName); if (thumbRes != 0) { mThumbs.add(thumbRes); mImages.add(res); } } } } private class ImageAdapter extends BaseAdapter implements ListAdapter, SpinnerAdapter { //适配器,烦恼了我很久的东西 private LayoutInflater mLayoutInflater; ImageAdapter(Activity activity) { mLayoutInflater = activity.getLayoutInflater(); } public int getCount() { return mThumbs.size(); } public Object getItem(int position) { return position; } public long getItemId(int position) { return position; } public View getView(int position, View convertView, ViewGroup parent) { View view; //先获得小图的布局文件,然后用一个image来填充图。最后返回该布局文件View if (convertView == null) { view = mLayoutInflater.inflate(R.layout.wallpaper_item, parent, false); } else { view = convertView; } ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image); int thumbRes = mThumbs.get(position); image.setImageResource(thumbRes); Drawable thumbDrawable = image.getDrawable(); thumbDrawable.setDither(true); //大幅减少图片的失真 return view; } } class WallpaperLoader extends AsyncTask<Integer, Void, Bitmap> { //AsyncTask是抽象类.AsyncTask定义了三种泛型类型 Params,Progress和Result。 //Params 启动任务执行的输入参数,比如HTTP请求的URL。这里是int //Progress 后台任务执行的百分比。 //Result 后台执行任务最终返回的结果,比如String。这里是Bitmap //相当于一个小的线程,在背后偷偷运行一点不复杂的东西 BitmapFactory.Options mOptions; WallpaperLoader() { mOptions = new BitmapFactory.Options(); mOptions.inDither = false; mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; } @Override protected Bitmap doInBackground(Integer... params) { if (isCancelled() || !isAdded()) { return null; } try { return BitmapFactory.decodeResource(getResources(), mImages.get(params[0]), mOptions); } catch (OutOfMemoryError e) { return null; } } //为后面的Excute定义操作 @Override protected void onPostExecute(Bitmap b) { if (b == null) return; if (!isCancelled() && !mOptions.mCancel) { // Help the GC if (mBitmap != null) { mBitmap.recycle(); } View v = getView(); if (v != null) { mBitmap = b; mWallpaperDrawable.setBitmap(b); v.postInvalidate(); } else { mBitmap = null; mWallpaperDrawable.setBitmap(null); } mLoader = null; } else { b.recycle(); } } void cancel() { mOptions.requestCancelDecode(); super.cancel(true); } } //该方法实现了在背景区域上的图像绘制 static class WallpaperDrawable extends Drawable { Bitmap mBitmap; int mIntrinsicWidth; int mIntrinsicHeight; /* package */void setBitmap(Bitmap bitmap) { mBitmap = bitmap; if (mBitmap == null) return; mIntrinsicWidth = mBitmap.getWidth(); mIntrinsicHeight = mBitmap.getHeight(); } @Override public void draw(Canvas canvas) { if (mBitmap == null) return; int width = canvas.getWidth(); int height = canvas.getHeight(); int x = (width - mIntrinsicWidth) / 2; Log.i("bruce","width " + width); Log.i("bruce","mIntrinsicWidth "+mIntrinsicWidth); int y = (height - mIntrinsicHeight) / 2; Log.i("bruce","height "+height); Log.i("bruce","mIntrinsicHeight "+mIntrinsicHeight); canvas.drawBitmap(mBitmap, x, y, null); } @Override public int getOpacity() { return android.graphics.PixelFormat.OPAQUE; } @Override public void setAlpha(int alpha) { // Ignore } @Override public void setColorFilter(ColorFilter cf) { // Ignore } } @Override public void onNothingSelected(AdapterView<?> parent) { } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectWallpaper(position); } // Selection handler for the embedded Gallery view @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if (mLoader != null ) { mLoader.cancel(); } mLoader = (WallpaperLoader) new WallpaperLoader().execute(position); } @Override public void onDestroy() { Log.i("bruce","onDestroy"); super.onDestroy(); if (mLoader != null && mLoader.getStatus() != WallpaperLoader.Status.FINISHED) { mLoader.cancel(true); mLoader = null; } } }布局文件中的WallpaperLoader和WallpaperDrawable方法现在暂时还没有细看,等有时间了会继续研究下。
至于事件和处理,他们相比于布局而言简单很多。事件就是一个button的单击。处理需要在Launcher中进行,是简单的布局文件背景设置。这里我就不列源码赘述了。
下一步的工作就是学习savedInstanceState了,将选择的结果存储到savedInstanceState中,然后这个小小的研究就可以宣告一段落了。