Вторник, 12.12.2017, 09:17
Поиск
Никнэйм
Сертификат на никнейм Olelucoye, зарегистрирован на Тимофеев Константин Михайлович
Зарегистрируй свой никнейм
Обратная связь
olelucoye.tk@yandex.ru
Реклама AdSense
Реклама
Друзья сайта
  • Лига медицинского права
  • Гостиница "Зай"
  • FAQ по системе
  • Инструкции для uCoz
  • Главная » Статьи » Андроид разработка

    Создание виджета - электронные часы с кастомным шрифтом

    Создание виджета - электронные часы с кастомным шрифтом.

    Создадим проект. Поскольку кодить будем виджет, при создании проекта убираем галочку Create Activity.

    Для виджета создадим layout  файл - в нем у нас будет два элемента ImageView для отображения времени и TextView для даты.

    Сразу возникает вопрос - зачем для отображения времени используем ImageView. Дело в том, что для изменения параметров элементов макета виджета мы не можем обратиться к ним напрямую. Для этого используется класс RemoteViews. Для изменения View-элементов в этом классе есть несколько прямых методов, типа setTextViewText(int viewID, CharSequence  text), и несколько неявных методов как setInt(int viewID, String methodName, int value), где имя метода нужно указывать в виде строки в передаваемых параметрах. Но нет метода для установки для элемента кастомного шрифта. Для этого придется идти на хитрость - текст будем преобразовывать в картинку и устанавливать её в ImageView. Способ этот я нашел на Хабре

    Итак , вот clock_layout.xml:

    <?xml version="1.0" encoding="utf-8"?>

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:orientation="vertical"

        android:id="@+id/llBack" >

        <ImageView

            android:id="@+id/ivTime"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:layout_weight="2"

            android:scaleType="fitXY" />

        <TextView

            android:id="@+id/tvDate"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:layout_weight="1"

            android:gravity="center"/>

    </LinearLayout>

    Теперь в папке xml создаем файл метаданных widget_metadata.xml:

    <?xml version="1.0" encoding="utf-8"?>

    <appwidget-provider

           xmlns:android="http://schemas.android.com/apk/res/android"

           android:initialLayout="@layout/clock_layout"

           android:minHeight="40dp"

           android:minWidth="110dp"

           android:updatePeriodMillis="0"

           android:configure="ru.urok.digitclock.ConfigActivity"

           android:previewImage="@drawable/preview">

    </appwidget-provider>

    В initialLayout указываем layout файл виджета. Далее указываем размеры виджета. Как вычислять размеры виджета написано много, поэтому скажу кратко вычисляется по формуле 70 * n – 30, где n – это количество ячеек. Есть еще формула 74 * n - 2, но она используется для API ниже 14.

    updatePeriodMillis - это период обновления виджета в миллисекундах, но поскольку минимальное время обновления виджета системой 30 минут (даже если укажем меньше), а нам нужно обновлять часы раз в минуту - ставим ноль, чтобы система не заморачивалась с обновлением виджета, обновлять будем сами.

    configure - здесь указываем полный путь к конфигурационному активити для виджета, которое сделаем позже. Если нет необходимости настраивать виджет перед установкой можно удалить эту строку и не создавать конфигурационное активити.

    previewImage - здесь указываем изображение, которое система нам покажет в списке виджетов.

    Создаем новый класс WidgetClock.java с суперклассом AppWidgetProvider, который в свою очередь является наследником BroadcastReceiver.

    У него есть четыре метода: onEnabled, onUpdate, onDeleted, onDisabled.

    onEnabled - он вызывается, когда создается первый экземпляр виджета. В нем мы настроим обновление виджета.

             @Override

             public void onEnabled(Context context) {

               super.onEnabled(context);

               long time = (long) Math.rint(System.currentTimeMillis()/60000);

               time = time*60000 + 60000;

               Intent intent = new Intent(context, WidgetClock.class);

               intent.setAction("update_all_widgets");

               PendingIntent pIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

               AlarmManager alarmManager = (AlarmManager) context

                   .getSystemService(Context.ALARM_SERVICE);

               alarmManager.setRepeating(AlarmManager.RTC, time,

                   60000, pIntent);

             }

    Обновление будем вызывать alarmManager'ом. Начало отсчета alarmManager'а (переменная time) устанавливаем так, чтобы обновления происходили точно в тот момент, когда системное время начинает отсчет следующей минуты, чтобы время на виджете менялось синхронно с системными часами. Создаем Intent с action - "update_all_widget". Упаковываем его в PendingIntent. Период повторения 60000 миллисекунд, т.е. одна минута. Всё это помещаем в метод setRepeating alarmManager'а. Теперь в начале каждой минуты будет срабатывать будильник и посылать сообщение "update_all_widget".

    Ловить его будем в методе onReceive (вспоминаем, что наш класс наследник BroadcastReceiver).

             @Override

             public void onReceive(Context context, Intent intent) {

               super.onReceive(context, intent);

               if(intent.getAction().equalsIgnoreCase("update_all_widgets")){

                  ComponentName thisAppWidget = new ComponentName(

                                  context.getPackageName(), getClass().getName());

                 AppWidgetManager appWidgetManager = AppWidgetManager

                                  .getInstance(context);

                 int ids[] = appWidgetManager.getAppWidgetIds(thisAppWidget);

                 onUpdate(context, appWidgetManager, ids);

               }

             }

    Здесь мы получаем массив ID всех виджетов и вызываем метод onUpdate.

    onUpdate - вызывается для обновления виджетов. В параметрах получает массив ID обновляемых виджетов.

    @Override

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[]                   appWidgetIds) {

               super.onUpdate(context, appWidgetManager, appWidgetIds);

               sp = context.getSharedPreferences(

                       "pref_digitclock", Context.MODE_PRIVATE);

               c = Calendar.getInstance();

               hour = c.get(Calendar.HOUR_OF_DAY);

               min = c.get(Calendar.MINUTE);

               day = c.get(Calendar.DAY_OF_MONTH);

               month = c.get(Calendar.MONTH);

               year = c.get(Calendar.YEAR);

               String h = Integer.toString(hour);

               String m = Integer.toString(min);

               if(h.length()<2)h = "0" + h;

               if(m.length()<2)m = "0" + m;

               time = h + ":" + m;

               date = Integer.toString(day) +" " + MonthAsString(month, context) + " " +                                                                                                                       Integer.toString(year);

              for (int id : appWidgetIds) {

                  widgetUp(context, appWidgetManager, id);

              }

             }

    В этом методе получим текущие час, минуту,  день месяца, месяц, год. Сформируем из них строки. Месяц при этом из числового значения преобразуем в текст методом MonthAsString.

           private String MonthAsString(int month, Context context){

                        switch(month){

                        case 0:

                               return context.getString(R.string.Jan);

                        case 1:

                               return context.getString(R.string.Feb);

                        case 2:

                               return context.getString(R.string.Mar);

                        case 3:

                               return context.getString(R.string.Apr);

                        case 4:

                               return context.getString(R.string.May);

                        case 5:

                               return context.getString(R.string.Jun);

                        case 6:

                               return context.getString(R.string.Jul);

                        case 7:

                               return context.getString(R.string.Aug);

                        case 8:

                               return context.getString(R.string.Sep);

                        case 9:

                               return context.getString(R.string.Okt);

                        case 10:

                               return context.getString(R.string.Nov);

                        case 11:

                               return context.getString(R.string.Dec);

                        default:

                               return "Err";

                        }

             }

    Здесь все просто - в зависимости от номера месяца возвращаем соответствующую строковую константу из файла ресурсов string.xml

    <resources>

        <string name="app_name">DigitClock</string>

        <string name="label_widget">DigitClock</string>

        <string name="Jan">Января</string>

        <string name="Feb">Февраля</string>

        <string name="Mar">Марта</string>

        <string name="Apr">Апреля</string>

        <string name="May">Мая</string>

        <string name="Jun">Июня</string>

        <string name="Jul">Июля</string>

        <string name="Aug">Августа</string>

        <string name="Sep">Сентября</string>

        <string name="Okt">Октября</string>

        <string name="Nov">Ноября</string>

        <string name="Dec">Декабря</string>

        <string name="Text_color">Цвет текста</string>

        <string name="Backgr">Фон</string>

    </resources>

    Далее в цикле для каждого виджета вызываем метод widgetUp, в котором изменяем элементы интерфейса виджета.

    static void widgetUp(Context context, AppWidgetManager appWidgetManager, int widgetID){

                 RemoteViews widgetView = new RemoteViews(context.getPackageName(), R.layout.clock_layout);

                 colorText = sp.getInt("dg_color" + widgetID, Color.WHITE);

                 backgr = sp.getInt("dg_backgr" + widgetID, 0);

                 widgetView.setImageViewBitmap(R.id.ivTime, convertToImg(time, context, colorText));

                 widgetView.setTextViewText(R.id.tvDate, date);

                 widgetView.setTextColor(R.id.tvDate, colorText);                                       

                 widgetView.setInt(R.id.llBack, "setBackgroundResource", backgr);

                 appWidgetManager.updateAppWidget(widgetID, widgetView);

                }

    Получаем объект RemoteViews для виджета. Из preference получаем настройки цвета и фона. Настройки будут создаваться при установке виджета на экран в конфигурационной активити, но об этом чуть позже. В ImageView ставим картинку с изображением текущего времени. Картинку из текста получаем с помощью функции convertToImg.

    static Bitmap convertToImg(String text, Context context, int colorText) {

                   Bitmap btmText = Bitmap.createBitmap(230, 60, Bitmap.Config.ARGB_4444);

                   Canvas cnvText = new Canvas(btmText);

                   Typeface tf = Typeface.createFromAsset(context.getAssets(),"fonts/a_LCDNovaObl.ttf");

                   Paint paint = new Paint();

                   paint.setAntiAlias(true);

                   paint.setSubpixelText(true);

                   paint.setTypeface(tf);

                   paint.setColor(colorText);

                   paint.setTextSize(50);

                   cnvText.drawText(text, 50, 50, paint);

                   return btmText;

               }

    Создаем bitmap, связываем его c canvas. Получаем шрифт из папки asset/font/. Создаем и настраиваем кисть. Пишем на canvas'е текущее время и возвращаем полученный bitmap.

    Вот это и есть хитрость, о которой я говорил вначале - надпись получается тем шрифтом, который мы установим в кисть.

    Дальше устанавливаем дату в TextView, цвет текста и фон стандартными методами. И применяем все обновления для текущего экземпляра виджета.

    onDeleted - вызывается при удалении экземпляра виджета.

             @Override

             public void onDeleted(Context context, int[] appWidgetIds) {

                   super.onDeleted(context, appWidgetIds);

                   Editor editor = context.getSharedPreferences("pref_digitclock", Context.MODE_PRIVATE).edit();

                   for (int widgetID : appWidgetIds) {

                     editor.remove("dg_color" + widgetID);

                     editor.remove("dg_backgr" + widgetID);

                   }

                   editor.commit();

             }

    Об удалении виджета с экрана позаботится суперкласс, а нам нужно удалить настройки этого экземпляра из preferences.

    onDisabled - вызывается при удалении последнего экземпляра виджета с экрана.

             @Override

             public void onDisabled(Context context) {

               super.onDisabled(context);

               Intent intent = new Intent(context, WidgetClock.class);

               intent.setAction("update_all_widgets");

               PendingIntent pIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

               AlarmManager alarmManager = (AlarmManager)                                                                                                                             context.getSystemService(Context.ALARM_SERVICE);

               alarmManager.cancel(pIntent);

              }

    Отменяем будильник. С WidgetClock закончили. Теперь создадим конфигурационный экран, который будет появляться перед помещением виджета на рабочий стол.

    Сначала сделаем макет экрана config_digitclock.xml. Две радиогруппы для выбора цвета текста и фона и кнопка ОК.

    <?xml version="1.0" encoding="utf-8"?>

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:orientation="vertical" >

        <TextView

            android:id="@+id/textView1"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:gravity="center_horizontal"

            android:text="@string/Text_color" />

        <RadioGroup

            android:id="@+id/rgColor"

            android:layout_width="match_parent"

            android:layout_height="wrap_content" >

            <RadioButton

                android:id="@+id/radioRed"

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:background="@color/red"

                android:checked="true"/>

            <RadioButton

                android:id="@+id/radioGreen"

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:background="@color/green" />

            <RadioButton

                android:id="@+id/radioBlue"

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:background="@color/blue"/>

        </RadioGroup>

        <TextView

            android:id="@+id/textView2"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:gravity="center_horizontal"

            android:text="@string/Backgr" />

        <RadioGroup

            android:id="@+id/rgBack"

            android:layout_width="match_parent"

            android:layout_height="wrap_content" >

            <RadioButton

                android:id="@+id/radioBack1"

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:background="@drawable/fon1_1"

                android:checked="true"/>

            <RadioButton

                android:id="@+id/radioBack2"

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:background="@drawable/fon2_1"/>

        </RadioGroup>

        <Button

            android:id="@+id/btApply"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:text="OK"

            android:onClick="onApply" />

    </LinearLayout>

    Создаем ConfigActivity.java наследуясь от обычного Activity.

    В onCreate из Intent получаем ID экземпляра виджета и формируем Intent c отрицательным результатом, на случай, если что-то пойдет не так или пользователь отменит создание виджета.

             @Override

             protected void onCreate(Bundle savedInstanceState) {

               super.onCreate(savedInstanceState);

               requestWindowFeature(Window.FEATURE_NO_TITLE);

               Intent intent = getIntent();

               Bundle extras = intent.getExtras();

               if (extras != null) {

                 widgetID = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,

                     AppWidgetManager.INVALID_APPWIDGET_ID);

               }

               if (widgetID == AppWidgetManager.INVALID_APPWIDGET_ID) {

                 finish();

               }

               resultValue = new Intent();

               resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetID);

               setResult(RESULT_CANCELED, resultValue);

               setContentView(R.layout.config_digitclock);

             }

    Обратите внимание на строку - requestWindowFeature(Window.FEATURE_NO_TITLE) - это означает, что активити отобразится без заголовка.

    В обработчике нажатия кнопки ОК сохраняем настройки виджета в зависимости от выбранных пользователем радиокнопок. Затем вызываем обновление виджета. Если этого не сделать сразу то настройки виджета применятся только при следующем обновлении, т.е когда сработает AlarmManager. Устанавливаем положительный результат в Intent и закрываем активити.

           public void onApply(View v){

                 int colorText = Color.WHITE;

                 int selColor = ((RadioGroup) findViewById(R.id.rgColor)).getCheckedRadioButtonId();

                 switch(selColor){

                 case R.id.radioRed:

                        colorText = Color.parseColor(getString(R.color.red));

                        break;

                 case R.id.radioGreen:

                        colorText = Color.parseColor(getString(R.color.green));

                        break;

                 case R.id.radioBlue:

                        colorText = Color.parseColor(getString(R.color.blue));

                        break;

                 }

                 int backgr = R.drawable.fon1;

                 int selBackgr = ((RadioGroup) findViewById(R.id.rgBack)).getCheckedRadioButtonId();

                 switch(selBackgr){

                 case R.id.radioBack1:

                        backgr = R.drawable.fon1;

                        break;

                 case R.id.radioBack2:

                        backgr = R.drawable.fon2;

                        break;

                 }

               SharedPreferences sp = getSharedPreferences("pref_digitclock", MODE_PRIVATE);

               Editor editor = sp.edit();

               editor.putInt("dg_color" + widgetID, colorText);

               editor.putInt("dg_backgr" + widgetID, backgr);

               editor.commit();

               AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);

               WidgetClock.widgetUp(this, appWidgetManager, widgetID);

               setResult(RESULT_OK, resultValue);

               finish();

           }

    Заметьте, что цвета мы берем из ресурса color.xml.

    <?xml version="1.0" encoding="utf-8"?>

    <resources>

        <color name="red">#FF0000</color>

        <color name="green">#00FF00</color>

        <color name="blue">#0000FF</color>

    </resources>

    Осталось прописать классы виджета и конфигурационного экрана в манифест.

    Класс виджета указываем как ресивер, настраиваем для него IntentFilter для обновления и указываем файл метаданных.

    Класс конфигурационного экрана - активити. Для него тоже настраиваем IntentFilter, чтобы вызов происходил при установке виджета на экран. Также я поставил тему, чтобы активити выглядело как диалоговое окно (т.е. занимало не всю поверхность экрана), но это по желанию.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"

        package="ru.urok.digitclock"

        android:versionCode="1"

        android:versionName="1.0" >

        <uses-sdk

            android:minSdkVersion="14"

            android:targetSdkVersion="14" />

        <application

            android:allowBackup="true"

            android:icon="@drawable/ic_launcher"

            android:label="@string/app_name"

            android:theme="@style/AppTheme" >

            <receiver android:name="ru.urok.digitclock.WidgetClock"                                                                                                 android:icon="@drawable/ic_launcher"                                                                                                                       android:label="@string/label_widget">

                <intent-filter>

                    <action

                        android:name="android.appwidget.action.APPWIDGET_UPDATE">

                    </action>

                </intent-filter>

                <meta-data android:name="android.appwidget.provider"                                                                                                                              android:resource="@xml/widget_metadata"/>

            </receiver>

            <activity android:name="ConfigActivity"                                                                                                                                                       android:theme="@android:style/Theme.Dialog">

                <intent-filter>

                    <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>

                </intent-filter>

            </activity>

        </application>

    </manifest>

    Также не забываем закинуть в папку asset/fonts/ шрифт, который будем использовать, а в папку drawable два фоновых изображения и картинку для превью виджета. Взять можно здесь.

    Листинги исходных кодов смотрите здесь.

    А здесь можно взять готовый неподписанный apk-файл

    Вот несколько экземпляров виджетов.

    Создание кастомного View-элемента интерфейса.

    Программируем калькулятор на андроид. Урок 1.

    Андроид приложение - Списки.

    Пластилиновый калькулятор для Андроид.

    Пластилиновая история.

    Игровая мышь A4Tech XL-750BH

    Делаем полноценное приложение на VBA часть1

    Категория: Андроид разработка | Добавил: Olelucoye (10.02.2015)
    Просмотров: 3484
    | Теги: Виджет, java, кастомный шрифт, Android | Рейтинг: 0.0/0
    Всего комментариев: 0
    Добавлять комментарии могут только зарегистрированные пользователи.
    [ Регистрация | Вход ]
    Меню сайта
    Категории раздела
    Андроид разработка [23]
    libGDX [24]
    Мои андроид проекты [6]
    Excel [7]
    Железяки [5]
    Скрипты в блокноте [4]
    Разное [1]
    Форма входа
    Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0
    Яндекс Метрика
    Яндекс.Метрика