среда, 5 октября 2011 г.

Пишем свой виджет для Android


Суть статьи - написать простой виджет для своих нужд. Мой виджет отсчитывал оставшееся время до нового года. Т.к. новый год прошел - предлагаю каждому создать счетчик под свои нужды, а следующим шагом будет создание конфигурируемого виджета. ( Неплохо так, писал статью больше 3х месяцев =) )

Первым делом я начал искать что есть готового в Android для обратного отсчета времени. Оказалось, что существует системный класс CountDownTimer, который с лёгкостью справляется с задачей отсчета определенного времени, а как бонус - может выполнять определенные действия каждый заданный интервал. Исходя из этого вырисовывается следующий алгоритм - в onUpdate() виджета мы записываем старт счетчика. Частоту обновления виджета устанавливаем в 0. Теперь при включении виджета будет запускаться счетчик обратного отсчета.



Какой задать интервал выполнения действий для счетчика ? Пожалуй это будет одна минута. Во первых такой интервал будет меньше нагружать систему (и садить батарею), во вторых я думаю, что использование CountDownTimer в самом виджете вообще не правильно, поэтому будем стараться как можно реже использовать его события =)
Каждую минуту будет обновляться интерфейс виджета. Я решил не привязываться к счетчику и не высчитывать остаток времени, а брать данные об остатке минут, часов и дней прямо из системных констант. Для этого мы будем использовать системный класс Calendar.
Ну да ладно, меньше слов - больше действий ) Перейдем к написанию виджета. Я представлю сразу готовый код, чтобы можно было его рассмотреть.
NYCounter.java
package com.newyear.counter;
 
import java.util.Calendar;
import java.util.Date;
 
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.os.CountDownTimer;
import android.widget.RemoteViews;
 
public class NYCounter extends AppWidgetProvider{
 
  public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
 
  final Context context_t = context;
  final RemoteViews views = new RemoteViews(context_t.getPackageName(), R.layout.widget);
  final AppWidgetManager wm = appWidgetManager;
  final int[] ids = appWidgetIds;
 
  //запускаем счетчик до 1 Января 2012 (а доживем ли ? =) ), с выполнением действия каждую минуту 
  CountDownTimer timer = new CountDownTimer (dateToMilliSeconds(1,0,2012,0,0),60000) { 
 
    public void onTick(long millisUntilFinished) { //выполняем регулярное действие
 
      Calendar c = Calendar.getInstance();
      c.setTime(new Date());
      int doy = c.get(Calendar.DAY_OF_YEAR); //получаем нынешний день
      int hod = c.get(Calendar.HOUR_OF_DAY); // час
      int moh = c.get(Calendar.MINUTE); // минуту
      //int som = c.get(Calendar.SECOND); // секунду
      int diy = 0;
 
      if (isLeapViaPopeGregory (c.YEAR)) { // проверяем на высокосный год
        diy = 366;
      } else {
        diy = 365;
      }
 
      int daysLeft = diy - doy;
      String hoursLeft = Integer.toString(24 - hod);
      String minutesLeft = Integer.toString(60 - moh);
      //String secondsLeft = Integer.toString(60 - som);
 
      if (hoursLeft.length() < 2) {
        hoursLeft = "0" + hoursLeft;
      }
 
      if (minutesLeft.length() < 2) {
 minutesLeft = "0" + minutesLeft;
      }
 
 
      //меняем текст виджета
      views.setTextViewText(R.id.dayCounter, daysLeft + "");
      views.setTextViewText(R.id.hmCounter, hoursLeft + " : " + minutesLeft);
 
      wm.updateAppWidget(ids, views);
    }
 
  public void onFinish() {
  }
 
}.start();
}
 
  public long dateToMilliSeconds(int day, int month, int year, int hour, int minute)  {
 
    Calendar c = Calendar.getInstance();
    c.set(year, month, day, hour, minute, 00);
 
    return c.getTimeInMillis(); 
 
  }
 
  public static final boolean isLeapViaPopeGregory ( int yyyy ) {
    if ( yyyy < 0 ) return( yyyy + 1 ) % 4 == 0;
    if ( yyyy < 1582 ) return yyyy % 4 == 0;
    if ( yyyy % 4 != 0 ) return false;
    if ( yyyy % 100 != 0 ) return true;
    if ( yyyy % 400 != 0 ) return false;
    return true;
  }
}


Теперь нам нужно описать виджет и "приделать" к нему дизайн.

xml/widget_info.xml
<?xml version="1.0" encoding="UTF-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="220dp"
    android:minHeight="144dp"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/widget">
</appwidget-provider>

layout/widget.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/ball_red"
    >
   <TextView
         android:id="@+id/dayCounter"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:text="0"
         android:paddingLeft="70px"
         android:paddingTop="15px"
         android:textColor="#FFFFFF"
         android:textSize="80px"/>   
 <TextView
  android:id="@+id/hmCounter"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="00 : 00"
  android:paddingLeft="50px"
  android:textColor="#FFFFFF"
  android:textSize="50px"/>
 <TextView
  android:id="@+id/sCounter"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text=""
  android:paddingLeft="110px"
  android:textColor="#FFFFFF"
  android:textSize="30px"/>
</LinearLayout>

AndroidManifest .xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.newyear.counter"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/ball_red" android:label="@string/app_name">
        <receiver android:name="NYCounter" >
     <intent-filter>
         <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
     </intent-filter>
     <meta-data android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
 </receiver>
 
    </application>
</manifest>
Теперь нам осталось добавить лишь 2 вещи в проект - иконку и картинку, которая будет фоном для нашего виджета. Т.к. я делал новогодний виджет, то и картинка будет новогодней:
Собираем проект, проверяем на ошибки, запускаем на эмуляторе или на устройстве. Сразу предупреждаю, что виджет сырой и не претендует на звание "вот только так и нужно делать" ! Была поставлена задача, было найдено решение. Почти наверняка есть более удачные пути решения и я с удовольствием прочитаю их в комментариях.
В конечном счете должно получиться что-то похожее:
 

На момент написания кода и стилей для виджета все было ок, потому как число оставшихся дней было двухзначное ) Теперь вот не влазит... Это легко исправляется в стилях виджета путем уменьшения шрифта.
Я надеюсь развить тему этого виджета дальше и сделать его конфигурируемым. Хочется задавать самому фоновую картинку и дату/время отсчета. А пока на этом все, удачного кодинга!
Источник: http://ondroid.info

Unity3D, 2D платформер, персонаж прилипает к стене после прыжка.

     Если следовать логике физической модели Unity3D, когда на объект действует горизонтальная сила(в принципе она может быть в любом напра...