Android, операционные системы
0,0
рейтинг
29 августа 2013 в 15:19

Разработка → Android компонент с нуля — 2 — Лупа для изображения tutorial

Время от временя в интернете, в основном на сайтах различных магазинов и торговых площадок, мы сталкиваемся с программной лупой, позволяющей рассмотреть более детально фотографии различных товаров (что то подобное реализовано на сайте www.ebay.com). Созданием такой лупы для платформы Android мы и займёмся (специально для новичков).

Задание: Разработать компонент-лупу для более детального рассмотрения фотографий. Изначально на экране показана картинка, размеры которой пропорциональны экрану устройства (картинка показана на экране полностью). При касании пальца на экране, должно произойти увеличение картинки и показ данной области относительно касания, то есть при нажатии на цент, должна появиться увеличенная центральная часть картинки, а при нажатии на правый верхний угол, должна быть показана увеличенная часть правой верхней части и т. п.

Подготовка


Создайте новый проект и класс «PZoom», в качестве предка используйте класс View.
Нам интересны следующие функции:
PZoom — конструктор класса;
onDraw — функция рисования, вызывается каждый раз когда необходимо перерисовать компонент;
onTouchEvent — функция отслеживания внешних событий, вызывается каждый раз когда пользователь нажимает пальцем на экран и поподает на компонент, а также когда водит пальцем по компоненту и также отжимает палец.
Упростим себе задачу, будем использовать только одну картинку заранее помещённую в папку Drawable с именем «img» (чем больше будет картинка тем более наглядный эффект вы получите). В началекласса объявим новый объект типа Drawable
private Drawable image; // Главная картинка

Далее в конструкторе, поместим в image нужную картинку и установим рамки:
image = context.getResources().getDrawable(R.drawable.img);
image.setBounds(0, 0, image.getIntrinsicWidth(), image.getIntrinsicHeight());

Теперь пересоздадим процедуру рисования:
	@Override
	protected void onDraw(Canvas canvas) {
		 super.onDraw(canvas);
		 canvas.save(); // Сохранение тщекущих матриц (смещения, поворота и т.д....)

		 image.draw(canvas); // Указываем поверхность для рисования картинки
		 canvas.restore(); // Очищение матриц, приведение их в исходное состояние
	}

Переходим в главный класс приложения и модифицируем конструктор класса следующим образом:
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(new PZoom(this));
	}


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

Функционал


Для начала сделаем так чтобы по умолчанию картинка помещалась посреди экрана а её размеры были пропорциональны экрану. Для этого напишем отдельную функцию SetCenter, определяющую позицию картинки на поверхности, а также правильный множитель для пропорции.
Объявим дополнительные переменные в начале класса:
	int X = 0,Y = 0; // Позиции картинки на экране
	float scale = 0; // Множитель для пропорций
	int pX = 0, pY = 0; // Позиция пальца на экране
	int dWidth = 0, dHeight = 0; // Размеры компонента

Теперь тело самой функции (разжёвывать её нет смысла, это элементарная математика):
	void SetCenter(){
		// Вычисляем множитель для пропорции
		if(dWidth>dHeight) // 
			scale = (float)dHeight/image.getIntrinsicHeight();
		else
			scale = (float)dWidth/image.getIntrinsicWidth();

		// Позиция на экране
		if((dWidth-image.getIntrinsicWidth()*scale)>0){
			X = (int)((dWidth-(image.getIntrinsicWidth()*scale))/2/scale);
			Y = 0;
		} else {
			X = 0;
			Y = (int)((dHeight-(image.getIntrinsicHeight()*scale))/2/scale);
		}
	}

Теперь дополним функцию рисования виджета:
@Override	
	protected void onDraw(Canvas canvas) {
		 super.onDraw(canvas);
		 canvas.save(); // Сохранение тщекущих матриц (смещения, поворота и т.д....)
		 
		 if (dWidth==0){ // Проверка наличия данных в переменных размера виджета
			 dWidth = canvas.getWidth();
			 dHeight = canvas.getHeight();
		 }
		 
		 if (pX==0) { // Проверяем  позицию курсора
				SetCenter(); // Устанавливаем картинку в центре виджета
			}
		 
		 canvas.scale(scale, scale); // ZooM
		 canvas.translate(X,Y); // Смещение картинки
		 
		 image.draw(canvas); // Указываем поверхность для рисования картинки
		 canvas.restore(); // Очищение матриц, приведение их в исходное состояние
	}

В начале происходит проверка наличия данных в переменных размера виджета, если они равны нулю, то узнаём размеры виджета. Далее происходит проверка наличия данных в переменной позиции курсора, если она ровна нулю (палец пользователя не находится на виджете) то запускаем функцию центровки картинки. После этого происходит изменение размеров картинки в соответствии со значением растяжки. Затем происходит смещение картики. В конце отрисовывается картинка и сбрасываются матрицы.
Переходим к самому главному — изменению размера и положения картинки относительно нажатого пальца. Прево наперво следует переопределить процедуру позиционирования пальца на экране onTouchEvent:
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		//Вытягиваем совершённое действие
		 pX=(int) event.getX();	// Позиция по X
		 pY=(int) event.getY();	// Позиция по Y
		 int Action=event.getAction();	// Действие
	 
		 // Поднятие пальца
		 if (Action==MotionEvent.ACTION_UP){
			 pX = 0;
			 pY = 0;
		 }

		 invalidate(); // Перерисовка виджета
		 return true;
	 }

Описывать не буду, кому интересно то обратитесь к первой статье. Осталось создать процедуру вычисления новой позиции картинки на экране, она будет иметь следующий вид:
	void NewPosition(){
		scale = 1; // Устанавливаем коэфициент растягивания
		
		// Вычисляем новые позиции картинки относительно экрана и нажатого пальца
		X = (int)((float)-image.getIntrinsicWidth()*((float)pX/dWidth)+((float)dWidth*((float)pX/dWidth)));
		Y = (int)((float)-image.getIntrinsicHeight()*((float)pY/dHeight)+((float)dHeight*((float)pY/dHeight)));
		
		// Проверка чтобы небыло сверху и слева белых мест
		if (X>0) X=0;
		if (Y>0) Y=0;
	}

Слегка меняем функцию отрисовки видежта, а именно — если переменная позиции курсора не пуста, то вычисляем новую позицию:
if (pX==0) { // Проверяем  позицию курсора
				SetCenter(); // Устанавливаем картинку в центре виджета
			} else {
				NewPosition(); // Устанавливаем картинку в новую позицию
			}

Если всё сделали правильно, то при нажатии на картинку она будет увеличиваться и перемещаться по экрану относительно пальца пользователя. Удачного тестирования!

Конечный вид класса картинки-лупы:
package com.gc986.photozoom;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.View;

public class PZoom extends View {
	private Drawable image; // Главная картинка
	int X = 0,Y = 0; // Позиции картинки на экране
	float scale = 0; // Множитель для пропорций
	int pX = 0, pY = 0; // Позиция пальца на экране
	int dWidth = 0, dHeight = 0; // Размеры компонента
	
	public PZoom(Context context) {
		super(context);
		// Создание объекта главной картинки
		image = context.getResources().getDrawable(R.drawable.img);
		image.setBounds(0, 0, image.getIntrinsicWidth(), image.getIntrinsicHeight());
	}
	
	@Override	
	protected void onDraw(Canvas canvas) {
		 super.onDraw(canvas);
		 canvas.save(); // Сохранение тщекущих матриц (смещения, поворота и т.д....)
		 if (dWidth==0){ // Проверка наличия данных в переменных размера виджета
			 dWidth = canvas.getWidth();
			 dHeight = canvas.getHeight();
		 }
		 
		 if (pX==0) { // Проверяем  позицию курсора
				SetCenter(); // Устанавливаем картинку в центре виджета
			} else {
				NewPosition(); // Устанавливаем картинку в новую позицию
			}
		 
		 canvas.scale(scale, scale); // ZooM
		 canvas.translate(X,Y); // Смещение картинки
		 
		 image.draw(canvas); // Указываем поверхность для рисования картинки
		 canvas.restore(); // Очищение матриц, приведение их в исходное состояние
	}

	/**Центровка картинки*/
	void SetCenter(){
		// Вычисляем множитель для пропорции
		if(dWidth>dHeight) // 
			scale = (float)dHeight/image.getIntrinsicHeight();
		else
			scale = (float)dWidth/image.getIntrinsicWidth();
		// Позиция на экране
		if((dWidth-image.getIntrinsicWidth()*scale)>0){
			X = (int)((dWidth-(image.getIntrinsicWidth()*scale))/2/scale);
			Y = 0;
		} else {
			X = 0;
			Y = (int)((dHeight-(image.getIntrinsicHeight()*scale))/2/scale);
		}
	}
	
	/**Определение новых позиций картинки*/
	void NewPosition(){
		scale = 1; // Устанавливаем коэфициент растягивания
		// Вычисляем новые позиции картинки относительно экрана и нажатого пальца
		X = (int)((float)-image.getIntrinsicWidth()*((float)pX/dWidth)+((float)dWidth*((float)pX/dWidth)));
		Y = (int)((float)-image.getIntrinsicHeight()*((float)pY/dHeight)+((float)dHeight*((float)pY/dHeight)));
		// Проверка чтобы небыло сверху и слева белых мест
		if (X>0) X=0;
		if (Y>0) Y=0;
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		//Вытягиваем совершённое действие
		 pX=(int) event.getX();	// Позиция по X
		 pY=(int) event.getY();	// Позиция по Y
		 int Action=event.getAction();	// Действие
		 // Поднятие пальца
		 if (Action==MotionEvent.ACTION_UP){
			 pX = 0;
			 pY = 0;
		 }

		 invalidate(); // Перерисовка виджета
		 return true;
	 }
}

Так должно получиться в идеале — http://youtu.be/GHB91_RzORY
Архив примера со всеми комментариями можете скачать по следующей ссылке — http://www.anprog.com/documents/Photo_Zoom.zip
Язовцев Игорь @SunSunSun
карма
12,0
рейтинг 0,0
Android, операционные системы
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (11)

  • +1
    Это не лупа, а зум.
  • +4
    Неужели у Вас руки сами не тянуться проставить везде одинаково пробелы :)?

    В одном месте кода между переменными есть пробел — в другом нет.
    int X = 0,Y = 0;
    int pX = 0, pY = 0;


    Или здесь… Нужны пробели между аргументами или нет?
    canvas.scale(scale, scale);
    canvas.translate(X,Y);


    Ну, я бы еще по бокам "==" проставил…
    if (dWidth==0){
  • 0
    Эх, я так надеялся, что здесь будет FrameLayout, который масштабирует своих детей :)
  • 0
    Ура, благодаря вам я написал своё первое приложение под Андроид, спасибо :)
    • 0
      Пожалуйста :)
    • 0
      Только в маркет его не выкладывайте, пожалуйста.
      • +1
        Да ладно, все же так делают, или вы хотите меня опередить? ))
        • 0
          Я хочу чтобы все так не делали)) там итак шлака полно.
  • 0
    Форматирование странное какое-то, во всех же IDE 1ой кнопкой можно правильно отформатировать весь проект.
  • 0
    Пожалуйста, почитайте code style guidelines.
  • 0
    Извините, но мое мнение что немного странная «лупа» получилась

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.