Android桌面小部件開發——月之眼時鐘

g347068j 8年前發布 | 20K 次閱讀 安卓開發 Android開發 移動開發

關于Android桌面小部件的官方教程當然就是 Android開發者文檔 ,這里以一個火影迷感興趣的圖騰設計一款桌面時鐘,拋磚引玉。

效果圖

效果圖|from Google Nexus 6P with Screen Size 1440x2560

準備素材

小部件預覽圖

素材-預覽圖|720x720

widget_bg.png

素材-鐘面|720x720

widget_hour_00.png

素材-時針|720x720

widget_min_00.png

素材-分針|720x720

widget_sec_00.png

素材-秒針|720x720

四張切圖使用AI繪制導出,規格均為720X720,放置于Android工程res/drawable-xxxhdpi目錄下,這樣時鐘大小較合適。

表一

name icon size scope 代表屏幕 scale
ldpi 36x36 0~120dpi 現今鮮有設備 0.75
mdpi 48x48 120~160dpi 320x480 1
hddpi 72x72 160~240dpi 480x800 1.5
xhdpi 96x96 240~320dpi 720x1280 2
xxhdpi 144x144 320~480dpi 1080x1920 3
xxxhdpi 192x192 480~640dpi 1440x2560 4

編寫時鐘布局 app_widget_clock.xml

注意,App Widget使用的是RemoteViews,僅支持有限的內置控件,自定義控件一律不支持,并且對控件的操作均要通過RemoteViews的有限方法來執行,接下來我們會用到RemoteViews的setImageViewBitmap方法,留意下面的代碼。

<?xml version="1.0" encoding="utf-8"?>
<!--Widget支持的控件-->
<!--FrameLayout-->
<!--LinearLayout-->
<!--RelativeLayout-->
<!--GridLayout-->
<!--AnalogClock-->
<!--Button-->
<!--Chronometer-->
<!--ImageButton-->
<!--ImageView-->
<!--ProgressBar-->
<!--TextView-->
<!--ViewFlipper-->
<!--ListView-->
<!--GridView-->
<!--StackView-->
<!--AdapterViewFlipper-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="8dp"
    >

  <!--表盤-->
  <ImageView
      android:id="@+id/background"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center"
      android:src="@drawable/widget_bg"
      />

  <!--秒針-->
  <ImageView
      android:id="@+id/time_s"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center"
      android:src="@drawable/widget_sec_00"
      />

  <!--分針-->
  <ImageView
      android:id="@+id/time_m"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center"
      android:src="@drawable/widget_min_00"
      />

  <!--時針-->
  <ImageView
      android:id="@+id/time_h"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center"
      android:src="@drawable/widget_hour_00"
      />
</FrameLayout>

編寫AppWidgetProvider

編寫ClockAppWidgetProvider.java

public class ClockAppWidgetProvider extends AppWidgetProvider {

  public ClockAppWidgetProvider() {
    super();
  }

  @Override
  public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    super.onUpdate(context, appWidgetManager, appWidgetIds);
    // 調用的間隔由res/xml/app_widget_info_clock.xml下的updatePeriodMillis決定
    // 下面的for循環是update app widgets的標準寫法
    // N是桌面上該小部件的數目
    final int N = appWidgetIds.length;
    for (int i = 0; i < N; i++) {
      // 對每一個小部件進行更新
      int appWidgetId = appWidgetIds[i];
      RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.app_widget_clock);
      appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
    }
    // TODO 啟動ClockService
  }

  @Override public void onDeleted(Context context, int[] appWidgetIds) {
    super.onDeleted(context, appWidgetIds);
    // TODO 任意一個小部件被移除時調用
  }

  @Override public void onEnabled(Context context) {
  }

  @Override public void onDisabled(Context context) {
    // 所有桌面小部件被移除時調用
    // TODO 注銷ClockService
  }
}

編寫res/xml/app_widget_info_clock.xml

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/app_widget_clock"
    android:minHeight="250dp"
    android:minWidth="250dp"
    android:previewImage="@drawable/widget_clock_preview"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="86400000"
    >
</appwidget-provider>
  • initialLayout初始化布局
  • minHeight & minWidth的設置參見 小部件設計
    // cellCountInRowOrColumn - 小部件的行數或列數
    valueInDP = 70 × cellCountInRowOrColumn ? 30
  • previewImage是小部件的預覽圖,長按桌面查看小部件列表時顯示的那個icon就是它
  • resizeMode可伸縮的方向
  • updatePeriodMillis小部件的刷新間隔,單位是秒,默認是一天

Manifest的聲明

<receiver android:name=".ui.widget.clock.ClockAppWidgetProvider">
  <intent-filter>
      <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
  </intent-filter>
  <meta-data
      android:name="android.appwidget.provider"
      android:resource="@xml/app_widget_info_clock"/>
</receiver>

編寫ClockService

ClockService用于接收系統時間,并根據時間的時分秒值轉動我們的時針、分針和秒針。

// 時分秒針角度推導
// 假設rawS、rawM、rawH分別為秒、分、時(12小時制)的數值
// angleS、angleM、angleH分別為秒、分、時針的轉動角度
angleS = 360 / 60 × rawS
       = 360 / 60 × rawS
       = 360 / 60 × realS
其中,令 realS = rawS

angleM = 360 / 60 × rawM + 360 / 3600 × rawS
       = 360 / 60 × (rawM + rawS / 60)
       = 360 / 60 × (rawM + realS / 60)
       = 360 / 60 × realM
其中,令 realM = rawM + realS / 60

angleH = 360 / 12 × rawH + 360 / 12 / 3600 × (60 × rawM + rawS)
       = 360 / 12 × (rawH + rawM / 60 + rawS / 3600)
       = 360 / 12 × (rawH + (rawM + rawS / 60) / 60)
       = 360 / 12 × (rawH + realM / 60)
       = 360 / 12 × realH
其中,令 realH = rawH + realM / 60

編寫時鐘任務

private final class MyTimerTask extends TimerTask {

  @Override public void run() {

    // 獲取Widgets管理器
    AppWidgetManager widgetManager = AppWidgetManager.getInstance(getApplicationContext());
    // widgetManager所操作的Widget對應的遠程視圖即當前Widget的layout文件
    RemoteViews remoteView = new RemoteViews(getPackageName(), R.layout.app_widget_clock);

    // 見公式推導
    Calendar calendar = Calendar.getInstance();
    int rawS = calendar.get(Calendar.SECOND);
    int rawM = calendar.get(Calendar.MINUTE);
    int rawH = calendar.get(Calendar.HOUR);

    float realS = rawS;
    float realM = rawM + realS / 60.0f;
    float realH = rawH + realM / 60.0f;

    // 計算時分秒針的角度
    float rotateS = 360f / 60f * realS;
    float rotateM = 360f / 60f * realM;
    float rotateH = 360f / 12f * realH;

    // 根據角度轉動時分秒針
    if (null == mHandS || mHandS.isRecycled()) {
      mHandS = BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.widget_sec_00);
    }
    if (null == mHandM || mHandM.isRecycled()) {
      mHandM = BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.widget_min_00);
    }
    if (null == mHandH || mHandH.isRecycled()) {
      mHandH = BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.widget_hour_00);
    }

    // RemoteViews的內置方法操作控件
    // 對內置控件的操作,RemoteViews僅提供有限的幾個方法,這里我們用到其中一個:setImageViewBitmap
    remoteView.setImageViewBitmap(R.id.time_s, rotateBitmap(mHandS, rotateS));
    remoteView.setImageViewBitmap(R.id.time_m, rotateBitmap(mHandM, rotateM));
    remoteView.setImageViewBitmap(R.id.time_h, rotateBitmap(mHandH, rotateH));

    // 當點擊Widgets時觸發的事件
    ComponentName componentName = new ComponentName(getApplicationContext(), ClockAppWidgetProvider.class);
    widgetManager.updateAppWidget(componentName, remoteView);
  }
}

Bitmap旋轉

/**
 * 旋轉 
 *
 * @param source
 * @param degree from 0f to 360f
 * @return
 */
private Bitmap rotateBitmap(Bitmap source, float degree) {
  if (null == source) {
    return null;
  }
  int size = source.getWidth();
  Matrix matrix = new Matrix();
  matrix.reset();
  matrix.setRotate(degree, size / 2, size / 2);
  return Bitmap.createBitmap(source, 0, 0, size, size, matrix, true);
}

ClockService啟動MyTimerTask

public class ClockService extends Service {

  private Timer mTimer;

  @Override public void onCreate() {
    super.onCreate();
    mTimer = new Timer();
    // 1000ms執行一次
    mTimer.schedule(new MyTimerTask(), 0, 1000);
  }

  // TODO 其它生命周期方法
}

記得在Manifest.xml里聲明ClockService

<service android:name=".ui.widget.clock.ClockService"/>

 

 

 

來自:http://www.jianshu.com/p/2e75b695459a

 

 本文由用戶 g347068j 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!