经过整整一周的学习,总算实现了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中,然后这个小小的研究就可以宣告一段落了。