主要功能就是: 每分钟更换一张背景图片达到报时的功能,提前30S准备壁纸 每到一分钟时画图。
本打算用多线程实现但是貌似多线程调度出现了点问题,所以改用单线程实现(因为这个壁纸程序比较简单只是每分钟更换一下壁纸没有其他特效单线程实现应该问题不大)
1.首先壁纸程序配置 AndroidManifest.xml
功能:告诉系统设置壁纸时启动的service 和壁纸程序settingActivity的配置文件wallpaper.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.clock"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="10" />
<!--添加权限-->
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<!-- 壁纸程序配置service -->
<service
android:name="com.clock.PicService"
android:permission="android.permission.BIND_WALLPAPER"
>
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/wallpaper" />
</service>
<!-- 壁纸程序配置activity -->
<activity android:name=".MmClockActivity"
android:label="@string/app_name"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>
</manifest>
2.接下来建立一个xml文件夹 建立wallpaper.xml
功能:配置点击settingActivity时启动的Activity
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:thumbnail="@drawable/icon"
android:description="@string/hello"
android:settingsActivity="com.clock.MmClockActivity" />
3.壁纸程序设置时的activity界面布局文件main.xml
功能:界面内容 显示声明 可编辑文本框和一个“设置路径”按钮点击时弹出一个Dialog选择路径
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:weightSum="1">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
android:layout_weight="0.04" android:textSize="20dp"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/statement" android:layout_weight="0.27" android:textSize="20dp"/>
<RelativeLayout android:layout_width="match_parent" android:id="@+id/relativeLayout1" android:layout_height="wrap_content">
<EditText android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/editText1" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:layout_toLeftOf="@+id/setImgPath"></EditText>
<Button android:id="@+id/setImgPath" android:layout_width="wrap_content" android:text="@string/setPath" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentRight="true"></Button>
</RelativeLayout>
</LinearLayout>
4.设置页面中点击“设置路径”按钮时弹出的对话框布局文件dialog.xml
功能:很简单只有一个listActivity
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" android:weightSum="1">
<ListView android:id="@+id/listView1" android:layout_height="fill_parent"
android:layout_width="fill_parent" android:layout_alignParentTop="true" android:layout_alignParentLeft="true"></ListView>
</RelativeLayout>
5.壁纸程序的配置activity类 MmClockActivity.class
功能:显示声明 弹出Dialog遍历文件夹(单击进入遍历子文件夹 ,长按设置路径关闭Dialog)把路径写入配置文件
package com.clock;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import android.widget.ListView;
/**
* 显示声明和设置图片路径
* @author Administrator
*
*/
public class MmClockActivity extends Activity {
private Button setBtn;//设置路径按钮
private EditText editText;//
private File currentDirectory = new File("/");//当前路径
private ListView m_ListView;//显示列表
private Dilg dlg;//对话框
private List<String> pathList;//路径列表
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);//布局文件
setBtn=(Button) findViewById(R.id.setImgPath);
editText=(EditText)findViewById(R.id.editText1);
setBtn.setOnClickListener(new btnListener());
// editText.setText("/sdcard/time");//默认值
readSharedPreferences();//读取配置文件
}
@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
/* 按返回键时保存数据 */
SharedPreferences uiState = getSharedPreferences("pathConfig", MODE_WORLD_READABLE);
SharedPreferences.Editor editor = uiState.edit();// 取得编辑对象
editor.putString("imgPath",editText.getText().toString());// 添加值
editor.commit();// 提交保存
PicService.mHandler.sendEmptyMessage(31);//向主线程发送消息通知壁纸路径已经更改
}
/**
* 读取配置文件
*/
public void readSharedPreferences(){
SharedPreferences settings = getSharedPreferences("pathConfig", MODE_WORLD_READABLE);
editText.setText(settings.getString("imgPath", "/"));
if( editText.getText().toString().equals("/"))
editText.setText("/sdcard/time");
}
/**
* 继承对话框类
* @author Administrator
*
*/
class Dilg extends Dialog{
public Dilg(Context context) {
super(context);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pathList=new ArrayList<String>();
setContentView(R.layout.dialog);
m_ListView=(ListView)this.findViewById(R.id.listView1);
m_ListView.setAdapter(new ArrayAdapter<String>(this.getContext(), android.R.layout.simple_expandable_list_item_1,pathList));
//添加点击监听(点击进入下一目录)
m_ListView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
if(arg2==0){
upOneLevel();//返回上一目录
}else{
File clickedFile = null;
if(currentDirectory.getAbsolutePath().toString().endsWith("/"))
clickedFile = new File(currentDirectory.getAbsolutePath()+pathList.get(arg2));
else{
clickedFile = new File(currentDirectory.getAbsolutePath()+"/"+pathList.get(arg2));
}
if(clickedFile != null)
browseTo(clickedFile);//浏览点击文件夹的子目录
}
}
});
//添加长按点击监听(长按设置路径,关闭对话框)
m_ListView.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
if(arg2!=0){
if(currentDirectory.getAbsolutePath().toString().endsWith("/"))
editText.setText(currentDirectory.getAbsolutePath()+pathList.get(arg2));//显示当前路径
else{
editText.setText(currentDirectory.getAbsolutePath()+"/"+pathList.get(arg2));
}
dlg.dismiss();//关闭对话框
}
return false;
}
});
}
}
class btnListener implements OnClickListener{
@Override
public void onClick(View arg0) {
dlg=new Dilg(MmClockActivity.this);
dlg.show();//显示对话框
browseToRoot();//浏览根目录
}
}
//浏览文件系统的根目录
private void browseToRoot()
{
browseTo(new File("/"));
}
//返回上一级目录
private void upOneLevel()
{
if(this.currentDirectory.getParent() != null)
this.browseTo(this.currentDirectory.getParentFile());
}
//浏览指定的目录,如果是文件则进行打开操作
private void browseTo(final File file)
{
if(dlg!=null){
dlg.setTitle(file.getAbsolutePath());
}
if (file.isDirectory())
{
this.currentDirectory = file;
fill(file.listFiles());//遍历file的子列表
}
}
//这里可以理解为设置ListActivity的源
private void fill(File[] files)
{ List<String> pathListTemp=null;//路径列表(临时)
if(pathList.size()>0){
pathListTemp=pathList;//保存到临时缓存
pathList.removeAll(pathList);
pathList.add("../返回上一层");
}
try {
for (File currentFile : files)
{
//判断是一个文件夹还是一个文件
if (currentFile.isDirectory())
{
if(!currentFile.isHidden())//不是隐藏文件夹
pathList.add(currentFile.getName());
}
}//重新设置适配器(更新ListView显示内容)
} catch (Exception e) {
pathList=pathListTemp;//出错赋值回去
Toast.makeText(getApplicationContext(), "没有权限打开这个文件夹",
Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
m_ListView.setAdapter(new ArrayAdapter<String>(dlg.getContext(), android.R.layout.simple_expandable_list_item_1,pathList));
}
}
6.接下来是壁纸程序的重点Service类PicService.clsass
功能:读取配置文件中设置的图片路径,30S初始化图片 1分钟显示图片 DiceEngine为自定义壁纸引擎画图及控制时间线程(实际是一个线程)都是在这个壁纸引擎类中完成的是壁纸程序的核心。public void onVisibilityChanged(boolean visible)方法是壁纸显示状态改变时的回调函数,利用此函数控制时间线程(diceThread不是真正的启动一个线程 ,从打印出的线程id就可以看出。本来想新开一个时间线程但是有点问题就是壁纸程序二次启动时holder.lockCanvas()得不到Canvs总是null因为某些原因不能再研究这个程序了,等有时间在研究吧!!希望高人能够解决这个问题真正做到多线程)
package com.clock;
import java.io.InputStream;
import java.util.Calendar;
import com.clock.util.ReadImage;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.service.wallpaper.WallpaperService;
import android.util.DisplayMetrics;
import android.view.SurfaceHolder;
/**
* 画壁纸 调度时间线程
* @author Administrator
*
*/
public class PicService extends WallpaperService{
private Context mContext = PicService.this;//上下文环境
public static String hour="0";//小时
public static int min=0;//分钟
public static int second=0;//秒
public static Handler mHandler;//处理线程间的消息
private static ReadImage readImage;//读取图片工具
private static int screenHigh=0;//屏幕高度
private static int screenWidth=0;//屏幕宽度
private static String imgPath="";//图片路径
private static Bitmap backBmp;//背景图片
@Override
public Engine onCreateEngine() {
return new DiceEngine();
}
@Override
public void onCreate() {
super.onCreate();
DisplayMetrics dm2 = getResources().getDisplayMetrics();
screenHigh=dm2.heightPixels;
screenWidth=dm2.widthPixels;//取屏幕宽度
readImage=new ReadImage();
readSharedPreferences();
}
/**
* 读取配置文件
*/
public void readSharedPreferences(){
SharedPreferences settings = getSharedPreferences("pathConfig", MODE_WORLD_READABLE);
imgPath=settings.getString("imgPath", "/");
if(imgPath.equals("/"))
imgPath="/sdcard/time";
}
/**
* 重写壁纸引擎类
* @author Administrator
*
*/
public class DiceEngine extends Engine{
private float xoffset=0;//壁纸滑动时的偏移量
private String picName="";//图片名称
private Boolean isCreate=true;//时间线程是不是刚创建的
//handler和线程,这个线程主要用来画图
private Handler mHandler2 = new Handler(getMainLooper());
private Thread diceThread = new Thread(){
private int createTime;//创建时间
private int currnetMin;//当前取到的分钟
private int currentHour;//当前的小时
public void run(){
Calendar c=Calendar.getInstance();
currentHour=c.get(Calendar.HOUR_OF_DAY);
if(currentHour<10){
PicService.hour="0"+currentHour;
}else{
PicService.hour=currentHour+"";
}
currnetMin=c.get(Calendar.MINUTE);
PicService.second=c.get(Calendar.SECOND);
if(isCreate){//第一次运行
System.out.println("启动时的秒数 "+PicService.second+"----启动id=="+Thread.currentThread().getId());
createTime=PicService.second;//记录线程创建时的秒数
PicService.min=currnetMin;
initBack(0);
draw();
}else{
if(PicService.second==30){//提前30秒准备下一张壁纸
System.out.println("TimeThread-->"+Thread.currentThread().getId());
PicService.min=currnetMin;
initBack(30);
}
if(createTime>=30&&currnetMin!=PicService.min){//线程启动时>30秒的情况
PicService.min=currnetMin;
initBack(0);
draw();
createTime=0;
}else if(currnetMin!=PicService.min){//一分钟了
PicService.min=currnetMin;
draw();
}
}
isCreate=false;//运行过一次了
mHandler2.postDelayed(diceThread, 1000);//延时1000毫秒加入到队列
}
};
//构造
public DiceEngine(){
registMessage();//实例化Handler
}
/**
* 实例化本线程的Handler监听发送过来的消息
*/
public void registMessage(){
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {//收到线程消息时的回到函数
switch(msg.what){
case 31://读取配置文件得到配置路径
readSharedPreferences();//读取配置文件
break;
}
}
};
}
/**
* 初始化背景图片
* */
public void initBack(int state){
int tempMin;//临时变量用存放30秒时的下一分钟
if(backBmp != null){
if (!backBmp.isRecycled()){
backBmp.recycle();
}
}
if(state==30){
if(min==59)
tempMin=0;//30秒提前准备下一分钟的壁纸
else{
tempMin=min+1;
}
if(tempMin<10){
picName=hour+"0"+tempMin;
}else{
picName=hour+tempMin;
}
System.out.println("30s准备------id="+Thread.currentThread().getId());
}else{//不是30秒的情况
if(min<10){
picName=hour+"0"+min;
}else{
picName=hour+min;
}
System.out.println("即时准备------id="+Thread.currentThread().getId());
}
backBmp = readBmp(readImage.getPicture(picName+".jpg", imgPath));
}
/**
* 优化内存 读取图片
* */
public Bitmap readBmp(InputStream is){
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
//获取资源图片
if(is==null)
is =mContext.getResources().openRawResource(R.drawable.bg);
return BitmapFactory.decodeStream(is,null,opt);
}
/**
* 所有在屏幕上画图的操作都是在这个方法中完成
* */
public void draw(){
Rect rect=new Rect();//图片剪裁区
rect.top=0;rect.left=100;
rect.right=518; rect.bottom=450;
Rect dest=new Rect();//屏幕剪裁区
dest.top=0;dest.left=0;
dest.right=screenWidth; dest.bottom=screenHigh;
System.out.println("画图---id---id== "+Thread.currentThread().getId());
SurfaceHolder holder = getSurfaceHolder();
Canvas canvas = holder.lockCanvas();
canvas.drawBitmap(backBmp, rect, dest, null);//画图
holder.unlockCanvasAndPost(canvas);
}
@Override
public void onVisibilityChanged(boolean visible) {
// TODO Auto-generated method stub
super.onVisibilityChanged(visible);
if(visible){
//System.out.println("显示。。");
isCreate=true;
mHandler2.postDelayed(diceThread, 50);//延时50毫秒加入到队列
}else{
//System.out.println("隐藏。。");
mHandler2.removeCallbacks(diceThread);
releaseRes();
}
}
/**
* 释放资源
* */
public void releaseRes(){
if (!backBmp.isRecycled()) {
backBmp.recycle();
}
}
}
}
7.读取图片的工具类ReadImage.class
package com.clock.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
public class ReadImage {
/**
* 通过指定目录返回图片输入流
* @param path
* @return
*/
public InputStream getPicture(String fileName,String path){
//File.separator是文件分隔符
File file=new File(path + File.separator +fileName);
if(file.exists())
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
else
return null;
}
}
8.strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">minClock</string>
<string name="setPath">设置路径</string>
<string name="hello">一分钟换一个背景图片,实现报时功能</string>
<string name="statement">程序仅供学习交流,图片资源来自互联网如有违法请删除</string>
</resources>
OK 基本是所有代码了 程序比较简单 欢迎和初学android的同学交流共同进步!
源码下载地址:点击打开链接