Освой Android играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Экран

Настройки - Экран
Размеры экрана и его ориентация (Старый и новый способ)
Плотность экрана, масштабирование шрифта и др.
Получить текущее значение яркости экрана
Установить яркость экрана
Настраиваем яркость экрана в своём приложении
Определение поддерживаемых экранных размеров в манифесте
Размеры картинок для фона экрана

Небольшая подборка различных примеров для работы с экраном. На самом деле их не так часто приходится использовать в практике, но иметь общее представление бывает необходимым. Начало было положено в 2012 году, что-то могло устареть.

Настройки - Экран

Чтобы показать окно Экран из системного приложения Настройки:


// Kotlin
val intent = Intent(Settings.ACTION_DISPLAY_SETTINGS)
if (intent.resolveActivity(packageManager) != null) {
    startActivity(intent)
}

// Java Intent intent = new Intent(Settings.ACTION_DISPLAY_SETTINGS); if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); }

Размеры экрана и его ориентация (Старый и новый способ)

Чтобы узнать размеры экрана и его ориентацию из запущенного приложения, можно воспользоваться встроенными классами Android.


public void onClick(View v) {
    // Получим Display из the WindowManager
    Display display = ((WindowManager) getSystemService(WINDOW_SERVICE))
            .getDefaultDisplay();

    // Теперь получим необходимую информацию
    String width = Integer.toString(display.getWidth());
    String height = Integer.toString(display.getHeight());
    String orientation = Integer.toString(display.getOrientation());
    String info = "Ширина: " + width + "; Высота: " + height
            + "; Ориентация: " + orientation;

    TextView infoTextView = findViewById(R.id.textViewInfo);
    infoTextView.setText(info);
}

Данный способ был опубликован в те времена, когда у меня был Android 2.3. Читатели подсказали, что теперь методы считаются устаревшими (API 13 и выше). Пришлось переписывать код. Впрочем, спустя некоторое время и этот код стал считаться устаревшим.


// Kotlin
val display: Display = windowManager.defaultDisplay
val point = Point()
display.getSize(point)
val screenWidth: Int = point.x
val screenHeight: Int = point.y

// Теперь получим необходимую информацию
val width = Integer.toString(screenWidth)
val height = Integer.toString(screenHeight)

val info = "Ширина: $width; Высота: $height"

Log.i("Screen", info)

// Java public void onClick(View v) { Display display = getWindowManager().getDefaultDisplay(); Point point = new Point(); display.getSize(point); int screenWidth = point.x; int screenHeight = point.y; // Теперь получим необходимую информацию String width = Integer.toString(screenWidth); String height = Integer.toString(screenHeight); String info = "Ширина: " + width + "; Высота: " + height; TextView infoTextView = findViewById(R.id.textViewInfo); infoTextView.setText(info); }

Ориентацию при помощи нового метода не узнаешь. Помните, что это размеры экрана устройства, а не экрана вашего приложения. Кроме того, в документации как-то туманно описывается точность вычислений этих размеров. Никому верить нельзя.

Плотность экрана, масштабирование шрифта и др.

Существует класс DisplayMetrics, также имеющий в своём составе свойства для экрана. Пример также пришлось переписывать после выхода Android 11 (API 30), который теперь тоже устаревший:


// Kotlin
button.setOnClickListener {
    var screen = ""
    val metrics = DisplayMetrics()

    if (Build.VERSION.SDK_INT >= 30){
        display?.apply {
            getRealMetrics(metrics)
            screen = """
                Width: ${metrics.widthPixels} pixels
                Height: ${metrics.heightPixels} pixels 
                The Logical Density: ${metrics.density}  
                X Dimension: ${metrics.xdpi} dot/inch
                Y Dimension: ${metrics.ydpi} dot/inch
                The screen density expressed as dots-per-inch: ${metrics.densityDpi}
                A scaling factor for fonts displayed on the display: ${metrics.scaledDensity}
            """
        }
    }else{
        // getMetrics() method was deprecated in api level 30
        windowManager.defaultDisplay.getMetrics(metrics)
        screen = """
        Width: ${metrics.widthPixels} pixels
        Height: ${metrics.heightPixels} pixels 
        The Logical Density: ${metrics.density}  
        X Dimension: ${metrics.xdpi} dot/inch
        Y Dimension: ${metrics.ydpi} dot/inch
        The screen density expressed as dots-per-inch: ${metrics.densityDpi}
        A scaling factor for fonts displayed on the display: ${metrics.scaledDensity}"""
    }

    val infoTextView = findViewById<TextView>(R.id.textView)
    infoTextView.text = screen
}

// Java DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); String strScreen = ""; strScreen += "Width: " + String.valueOf(metrics.widthPixels) + " pixels" + "\n"; strScreen += "Height: " + String.valueOf(metrics.heightPixels) + " pixels" + "\n"; strScreen += "The Logical Density: " + String.valueOf(metrics.density) + "\n"; strScreen += "X Dimension: " + String.valueOf(metrics.xdpi) + " dot/inch" + "\n"; strScreen += "Y Dimension: " + String.valueOf(metrics.ydpi) + " dot/inch" + "\n"; strScreen += "The screen density expressed as dots-per-inch: " + metrics.densityDpi + "\n"; strScreen += "A scaling factor for fonts displayed on the display: " + metrics.scaledDensity + "\n"; TextView infoTextView = findViewById(R.id.textViewInfo); infoTextView.setText(strScreen);

Вот ещё несколько способов определения размеров:


if((getResources().getConfiguration().screenLayout &
		Configuration.SCREENLAYOUT_SIZE_LARGE) ==
		Configuration.SCREENLAYOUT_SIZE_LARGE){
		//do something for Screen layout large
}else{
	//do something
}

Такой же код, но с использованием дополнительной константы SCREENLAYOUT_SIZE_MASK:


// Определение размера экрана
int screenSize = getResources().getConfiguration().screenLayout &
        Configuration.SCREENLAYOUT_SIZE_MASK; 

// Если планшет 
if (screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE ||
        screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE ) 
    mIsPhone = false; // Не телефон. какая-то булева переменная

На Kotlin в виде отдельной функции.


private fun getScreenSizeCategory(): String {
    return when (resources.configuration.screenLayout
            and Configuration.SCREENLAYOUT_SIZE_MASK) {
        Configuration.SCREENLAYOUT_SIZE_XLARGE -> "XLarge screen"
        Configuration.SCREENLAYOUT_SIZE_LARGE -> "Large screen"
        Configuration.SCREENLAYOUT_SIZE_NORMAL -> "Normal size screen"
        Configuration.SCREENLAYOUT_SIZE_SMALL -> "Small size screen"
        Configuration.SCREENLAYOUT_SIZE_UNDEFINED -> "Undefined screen size"
        else -> "Error"
    }
}

Или так:


private final float LOW_LEVEL = 0.75f;
private final float MEDIUM_LEVEL = 1.0f;
private final float HIGH_LEVEL = 1.5f;

float level = getApplicationContext().getResources().getDisplayMetrics().density;

if(level == LOW_LEVEL){
    //do something here
}else if(level == MEDIUM_LEVEL){
    //do smoothing here
}else if(level == HIGH_LEVEL){
    //do something here
}

Заглянув в документацию, обнаружил, что можно обойтись без собственных констант. Они уже есть в Android. Оформил в виде отдельного метода.


private void getDeviceDensity(){
    int dpiDensity = getResources().getDisplayMetrics().densityDpi;
    switch(dpiDensity){
        case DisplayMetrics.DENSITY_LOW:
            Log.i("Density", "DENSITY_LOW");
            break;
        case DisplayMetrics.DENSITY_MEDIUM:
            Log.i("Density", "DENSITY_MEDIUM");
            break;
        case DisplayMetrics.DENSITY_HIGH:
             Log.i("Density", "DENSITY_HIGH");
            break;
        case DisplayMetrics.DENSITY_XHIGH:
            Log.i("Density", "DENSITY_XHIGH");
            break;
        case DisplayMetrics.DENSITY_XXHIGH:
            Log.i("Density", "DENSITY_XXHIGH");
            break;
        case DisplayMetrics.DENSITY_XXXHIGH:
            Log.i("Density", "DENSITY_XXXHIGH");
            break;
    }
}

Получить текущее значение яркости экрана

В настройках экрана можно установить желаемую яркость экрана при помощи ползунка, но при этом мы не знаем, сколько это в попугаях. Я открою вам секрет при помощи простого кода:


// Kotlin
try {
    val curBrightnessValue = Settings.System.getInt(
        contentResolver,
        Settings.System.SCREEN_BRIGHTNESS
    )
    Log.i("Screen", "Текущая яркость экрана: $curBrightnessValue")
} catch (e: SettingNotFoundException) {
    e.printStackTrace()
}

// Java // получаем текущее значение яркости при нажатии на кнопку public void onClick(View v) { TextView infoTextView = findViewById(R.id.textViewInfo); try { int curBrightnessValue = android.provider.Settings.System.getInt( getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS); infoTextView.setText("Текущая яркость экрана: " + curBrightnessValue); } catch (SettingNotFoundException e) { e.printStackTrace(); } }

Установить яркость экрана

Если можно получить значение текущей яркости экрана, значит можно и установить яркость. Для начала нужно установить разрешение на изменение настроек в манифесте:


<uses-permission android:name="android.permission.WRITE_SETTINGS"/>

Для настройки яркости нужно использовать параметр System.SCREEN_BRIGHTNESS. Добавим на форму кнопку, метку и ползунок. Код для установки яркости:


private float mBackLightValue = 0.5f; // значение по умолчанию

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    SeekBar backLightSeekBar = findViewById(R.id.seekBar);
    final TextView settingTextView = findViewById(R.id.textViewSetting);
    Button updateButton = findViewById(R.id.buttonUpdate);

    updateButton.setOnClickListener(new Button.OnClickListener() {

        @Override
        public void onClick(View view) {
            int sysBackLightValue = (int) (mBackLightValue * 255);

            android.provider.Settings.System.putInt(getContentResolver(),
                    android.provider.Settings.System.SCREEN_BRIGHTNESS,
                    sysBackLightValue);
        }
    });

    backLightSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            mBackLightValue = (float) progress / 100;
            settingTextView.setText(String.valueOf(mBackLightValue));

            WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
            layoutParams.screenBrightness = mBackLightValue;
            getWindow().setAttributes(layoutParams);
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {

        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {

        }
    });
}

Проверил старый пример времён Android 2.2 на эмуляторе с Android 10. Правила ужесточились. Теперь разрешение на изменение системных настроек выдаются только системным программам. Пока ещё есть лазейка, которой и воспользуемся. Новый пример написан на Kotlin. Добавим в манифест немного модифицированное разрешение.


<uses-permission android:name="android.permission.WRITE_SETTINGS"
    tools:ignore="ProtectedPermissions" />

Далее программа должна проверить возможность изменять системные настройки через метод canWrite(). Если такая возможность есть, то запрашиваем разрешение. Появится специальное окно, в котором пользователь должен подтвердить своё решение через переключатель. После этого нужно заново запустить программу, чтобы ползунок стал доступен. Теперь можете менять настройки.


package ru.alexanderklimov.brightness

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

        val brightness = getBrightness()
        seekBar.progress = brightness

        textView.text = "Screen Brightness: $brightness"

        val canWrite = Settings.System.canWrite(this)

        // If app has no permission to write system settings
        if (!canWrite) {
            seekBar.isEnabled = false
            allowWritePermission()
        }

        seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {

            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                textView.text = "Screen Brightness : $progress"
                setBrightness(progress)
            }

            override fun onStartTrackingTouch(seekBar: SeekBar) { }

            override fun onStopTrackingTouch(seekBar: SeekBar) { }
        })
    }

    private fun allowWritePermission() {
            val intent = Intent(
                Settings.ACTION_MANAGE_WRITE_SETTINGS,
                Uri.parse("package:$packageName")
            )
            startActivity(intent)
    }

    private fun getBrightness(): Int {
        return Settings.System.getInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS, 0)
    }

    fun setBrightness(value: Int) {
        Settings.System.putInt(
            this.contentResolver,
            Settings.System.SCREEN_BRIGHTNESS,
            value
        )
    }
}

Настраиваем яркость экрана в своём приложении

Существует возможность переопределить яркость экрана в пределах своего приложения. Я не смог придумать, где можно найти практическое применение, но вдруг вам пригодится. Для управления яркостью экрана воспользуемся элементом SeekBar.


<?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:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Установить яркость экрана"/>

    <SeekBar
        android:id="@+id/seekBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:max="100"
        android:progress="50"/>

    <TextView
        android:id="@+id/textViewSetting"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="0.50"/>

</LinearLayout>

За яркость экрана отвечает свойство LayoutParams.screenBrightness:


package ru.alexanderklimov.screen;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.WindowManager;
import android.widget.SeekBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SeekBar backLightSeekBar = findViewById(R.id.seekBar);
        final TextView settingTextView = findViewById(R.id.textViewSetting);

        backLightSeekBar
                .setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                    @Override
                    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                        float backLightValue = (float) progress / 100;
                        settingTextView.setText(String.valueOf(backLightValue));

                        WindowManager.LayoutParams layoutParams = getWindow()
                                .getAttributes();
                        layoutParams.screenBrightness = backLightValue;
                        getWindow().setAttributes(layoutParams);
                    }

                    @Override
                    public void onStartTrackingTouch(SeekBar seekBar) {

                    }

                    @Override
                    public void onStopTrackingTouch(SeekBar seekBar) {

                    }
                });
    }
}

Яркость экрана

Интересно, что когда выводил ползунок в значение 0, то эмулятор зависал с экраном блокировки. Вам следует учесть эту ситуацию и добавить условие:


if(backLightValue < 0.1)
{
    backLightValue = (float)0.1;
}

Опять столкнулся с проблемой. Пример работал на старых устройствах, а на некоторых устройства не работает. Но за эти годы мне ни разу не пришлось использовать этот способ, поэтому даже не стал искать причину. И кстати, ошибка со значением 0 уже не возникает (возможно из-за того, что сам пример не работает как раньше).

Определение поддерживаемых экранных размеров в манифесте

Не всегда предоставляется возможным написать приложение для всех возможных типов экранов. Вы можете использовать тег <supports-screens> в манифесте, чтобы указать, на устройствах с какими экранами может работать ваша программа.


<supports-screens
  android:smallScreens="false"
  android:normalScreens="true"
  android:largeScreens="true"
  android:anyDensity="true"
/>

В данном примере приводится поддержка нормальных и больших экранов. Маленьким экраном можно назвать любой дисплей с разрешением меньше, чем HVGA. Под большим экраном подразумевается такой, который значительно больше, чем у смартфона (например, у планшетов). Экран нормальных размеров имеет большинство смартфонов.

Атрибут anyDensity говорит о том, каким образом ваше приложение будет масштабироваться при отображении на устройствах с разной плотностью пикселов. Если вы учитываете это свойство экрана в своем интерфейсе, установите этому атрибуту значение true. При значении false Android будет использовать режим совместимости, пытаясь корректно масштабировать пользовательский интерфейс приложения. Как правило, это снижает качество изображения и приводит к артефактам при масштабировании. Для приложений, собранных с помощью SDK с API level 4 и выше, этот атрибут по умолчанию имеет значение true.

Размеры картинок для фона экрана

Если вы используете изображение в качестве фона, то нет смысла выводить очень большую картинку на устройстве с маленьким экраном. Можно подготовить разные размеры.

res/drawable-ldpi - 240x320
res/drawable-mdpi - 320x480
res/drawable-hdpi - 480x800
res/drawable-xhdpi - 640x960
res/drawable-xxhdpi - 960x1440
res/drawable-tvdpi - 1.33 * mdpi

Реклама