OlegTar
@OlegTar
программист .NET, Javascript, Perl

Android: странное поведение Canvas?

Делаю приложение для Андроид.
Сделал в нём собственной компонент View. В нём на канвасе нарисовано поле 10x10
При том пользователь видит только часть поля, остальную часть он может видеть скроля пальцем. Пользователь может подсвечивать какой-либо квадратик нажимая и отпуская на нём палец, но это к делу не относится.

4d47a0ee09e71c55796956512de90c44.jpg

Пользователь может пальцами увеличивать поле:
6d4ac3ce1b0558f6d8d9166ed4cf648d.jpg

Пользователь может и уменьшать поле, и тут-то проблема.
34f86ba2762a227efabb34cb7bc91f47.jpg

Когда ScaleFactor становится примерно меньше 0.5, то канвас начинает смещаться к правому нижнему углу компонента. Именно сам канвас, сам компонент как стоял в прежних границах так и стоит.

Увеличение/уменьшение изображения происходит с помощью команды Canvas.Scale.

Внимание, вопрос: можно ли убрать это смещение к правому нижнему углу?

Приложение пишу на C# (xamarin). Хотя код пишу на C#, мне будут полезны ответы и тех, кто пишет на для Андроида традиционно на Java.

Вот код компонента, откомментировал как мог:
Код
public class Table : View
	{
		private Paint p;//линия квадратиков
		private Paint fill_p;//цвет подкрашенного квадратика
		private Paint border_p;//граница видимой части канваса оранжевого цвета

		private float scale_factor = 1.0f;//Скэйл Фактор для канваса
		private readonly ScaleGestureDetector _scaleDetector;


		private string debug_text;
		private MotionEventActions last_action;

		private float x;//точка касания, нужна для определения какой квадратик подсвечивать
		private float y;

		private float last_x;//предыдущие точки касания элемента, нужны для скрола
		private float last_y;	

		private int margin = 50;
		private int cols   = 10;
		private int rows   = 10;

		public Table (Context context) :
			base (context)
		{
			Initialize ();

			p = new Paint ();
			p.Color = Color.White;
			p.TextSize = 25;

			fill_p = new Paint ();
			fill_p.SetStyle (Paint.Style.Fill);
			fill_p.Color = new Color(223, 136, 2);

			border_p = new Paint ();
			border_p.SetStyle (Paint.Style.Stroke);
			border_p.Color = new Color(223, 136, 2);

			_scaleDetector = new ScaleGestureDetector(context, new MyScaleListener(this));		
		}

		public override bool OnTouchEvent(MotionEvent ev)
		{
			_scaleDetector.OnTouchEvent (ev);

			MotionEventActions action = ev.Action & MotionEventActions.Mask;
			//int pointerIndex;
			Console.WriteLine (action.ToString ());



			switch (action) {
				/*запоминаем, где человек нажал квадратик, если следующее действие будет Move, то скроллим поле.
				Если следующее движение будет Up, то мы подсвечим квадратик.
				*/
				case MotionEventActions.Down:
					last_x = ev.GetX ();
					last_y = ev.GetY ();
				break;

			case MotionEventActions.Move:
				/*Скроллим*/
				float x1 = ev.GetX ();
				float y1 = ev.GetY ();
				float delta_x = -x1 + last_x;
				float delta_y = -y1 + last_y;					

				this.ScrollBy ((int)delta_x, (int)delta_y);
				Console.WriteLine ("scrollX = {0}, scrollY = {1}", this.ScrollX, this.ScrollY);

				/*Проверяем чтобы нельзя было скролить дальше, чем надо*/
				if (this.ScrollX < 0) {
					this.ScrollX = 0;
				}
				if (this.ScrollY < 0) {
					this.ScrollY = 0;
				}

                       /*недопускаем, чтобы можно было перескролить вправо и вниз*/
				int visible_width = margin * cols + 1;
				int visible_height = margin * cols + 1;

				visible_width = (int)(visible_width * scale_factor);
				visible_height = (int)(visible_height * scale_factor);

				int bound_x = (visible_width - this.LayoutParameters.Width);
				int bound_y = (visible_height - this.LayoutParameters.Height);

				Console.WriteLine ("visible_width = {0}", visible_width);

				if (this.ScrollX > bound_x) {
					this.ScrollX = bound_x;
				}

				if (this.ScrollY > bound_y) {
					this.ScrollY = bound_y;
				}

				break;

				/*Запоминаем координаты для подсвечивания нужного квадратика*/
				case MotionEventActions.Up:
				if (last_action == MotionEventActions.Down) {
					x = (ev.GetX () + this.ScrollX) / scale_factor;
					y = (ev.GetY () + this.ScrollY)/ scale_factor;					
					debug_text = String.Format ("x = {0}, y = {0}", x, y);
					Console.WriteLine (debug_text);
					Invalidate ();
				}
				break;
			}
			last_action = action;

			last_x = ev.GetX();
			last_y = ev.GetY();
						
			return true;
		}

		protected override void OnDraw (Android.Graphics.Canvas canvas)
		{
			/*Рисуем поле*/
			base.OnDraw (canvas);
			canvas.Save();
			/*сделал на всякий случай Смещение на 0, 0. Не помогло*/
			canvas.Translate (0, 0);
			/*Увеличиваем/уменьшаем*/
			canvas.Scale (scale_factor, scale_factor);

			/*Рисуем клетчатое поле*/
			int height = rows * margin;
			int width  = cols * margin;


			for (int i = 0; i < cols + 1; i++) {
				canvas.DrawLine (i * margin, 0, i * margin, height, p);
			}
			for (int i = 0; i < rows + 1; i++) {
				canvas.DrawLine (0, i * margin, width, i * margin, p);
			}

                  /*Находим отступы сверху и сбоку, чтобы номера писать ровно по центру квадратика
                  так как буквы имеют одинаковую ширину вычисляем эти значения только для строки "00"*/
			string text = "00";
			Rect bounds = new Rect();
			p.GetTextBounds (text, 0, text.Length, bounds);
			int padding_x = (margin - bounds.Width ())/2;
			int padding_y = (margin - bounds.Height ())/2; 

			/*Подсвечиваем выбранный квадратик*/
			int x_int = (int) Math.Floor(x / margin);
			int y_int = (int) Math.Floor(y / margin);
			Console.WriteLine ("x_int = {0}, y_int = {0}", x_int, y_int);

			canvas.DrawRect (new RectF (x_int * margin + 1, y_int * margin + 1, x_int * margin + margin - 1, y_int * margin + margin - 1), fill_p);				

             /*Рисуем номера*/
			for (int i = 0; i < rows; i++) {
				for (int j = 0; j < cols; j++) {
					string number = ((i * 10) + j).ToString("D2");

					canvas.DrawText (number, j * margin + padding_x, i * margin + margin - padding_y, p);
				}
			}
			canvas.Restore();


			/*Рисуем оранжевую рамку вокруг поля*/
			canvas.DrawLine (0, 0, this.Width - 1 + this.ScrollX, 0, border_p);//-
			canvas.DrawLine (0, 0, 0, this.Height + this.ScrollY, border_p);//|
			canvas.DrawLine (this.Width - 1 + this.ScrollX, 0, this.Width - 1 + this.ScrollX, this.Height - 1 + this.ScrollY, border_p);// |
			canvas.DrawLine (0, this.Height - 1 + this.ScrollY, this.Width - 1 + this.ScrollX, this.Height - 1 + this.ScrollY, border_p);//_

			Console.WriteLine (scale_factor);		
		}

		public Table (Context context, IAttributeSet attrs) :
			base (context, attrs)
		{
			Initialize ();
		}

		public Table (Context context, IAttributeSet attrs, int defStyle) :
			base (context, attrs, defStyle)
		{
			Initialize ();
		}

		void Initialize ()
		{
		}

		//Обработчик щипка для Zoom'а
		private class MyScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener
		{
			private Table view;

			public MyScaleListener(Table view)
			{
				this.view = view;
			}

			public override bool OnScale(ScaleGestureDetector detector)
			{
				view.scale_factor *= detector.ScaleFactor;
				if (view.scale_factor > 5.0f)
				{
					view.scale_factor = 5.0f;
				}
				if (view.scale_factor < 0.1f)
				{
					view.scale_factor = 0.1f;
				}
				Console.WriteLine ("view.scale_factor = {0}", view.scale_factor);			
			
				view.Invalidate ();
				view.RequestLayout ();
				return true;
			}
		}
	}

  • Вопрос задан
  • 4046 просмотров
Решения вопроса 1
@xotta6bl4
habrahabr.ru/post/151492 Здесь я в свое время описал работу с канвасом на джаве.
//унаследовались от ScaleGestureDetector.SimpleOnScaleGestureListener, чтобы не писать пустую реализацию ненужных
    //методов интерфейса OnScaleGestureListener
    private class MyScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        //обрабатываем "щипок" пальцами
        @Override
        public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
            float scaleFactor=scaleGestureDetector.getScaleFactor();//получаем значение зума относительно предыдущего состояния
            //получаем координаты фокальной точки - точки между пальцами
            float focusX=scaleGestureDetector.getFocusX();
            float focusY=scaleGestureDetector.getFocusY();
            //следим чтобы канвас не уменьшили меньше исходного размера и не допускаем увеличения больше чем в 2 раза
            if(mScaleFactor*scaleFactor>1 && mScaleFactor*scaleFactor<2){
                mScaleFactor *= scaleGestureDetector.getScaleFactor();
                canvasSize =viewSize*mScaleFactor;//изменяем хранимое в памяти значение размера канваса
                //используется при расчетах
                //по умолчанию после зума канвас отскролит в левый верхний угол.
                //Скролим канвас так, чтобы на экране оставалась
                //область канваса, над которой был жест зума
                //Для получения данной формулы достаточно школьных знаний математики (декартовы координаты).
                int scrollX=(int)((getScrollX()+focusX)*scaleFactor-focusX);
                scrollX=Math.min( Math.max(scrollX, 0), (int) canvasSize -viewSize);
                int scrollY=(int)((getScrollY()+focusY)*scaleFactor-focusY);
                scrollY=Math.min( Math.max(scrollY, 0), (int) canvasSize -viewSize);
                scrollTo(scrollX, scrollY);
            }
            //вызываем перерисовку принудительно
            invalidate();
            return true;
        }
    }
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы