Отличный ответ от hackbod напомнил мне, что мне нужно опубликовать решение, к которому я в итоге пришел. Обратите внимание, что это решение, которое работало для меня для приложения, которым я занимался в то время, может быть улучшено с помощью предложений hackbod. В частности, мне не нужно было обрабатывать сенсорные события, и до прочтения поста hackbod мне не приходило в голову, что, если я это сделаю, мне нужно будет также масштабировать их.
Напомним, что для моего приложения мне нужно было иметь большую диаграмму (в частности, планировку этажа здания) с наложенными на нее другими маленькими "маркерными" символами. Фоновая диаграмма и символы переднего плана все нарисованы с использованием векторной графики (то есть объектов Path () и Paint (), примененных к Canvas в методе onDraw ()). Причина желания создавать всю графику таким образом, а не просто использовать растровые ресурсы, заключается в том, что графика конвертируется во время выполнения с помощью моего конвертера SVG-изображений.
Требовалось, чтобы диаграмма и связанные с ней символы маркера были дочерними элементами ViewGroup и могли бы быть все вместе увеличены.
Большая часть кода выглядит беспорядочно (это была спешка для демонстрации), поэтому вместо того, чтобы просто копировать все это, вместо этого я попытаюсь просто объяснить, как я это сделал с соответствующими битами кода, приведенными в кавычках.
Прежде всего, у меня есть ZoomableRelativeLayout.
public class ZoomableRelativeLayout extends RelativeLayout { ...
Этот класс включает классы слушателей, которые расширяют ScaleGestureDetector и SimpleGestureListener, так что макет можно панорамировать и масштабировать. Особый интерес здесь представляет слушатель масштабного жеста, который устанавливает переменную масштабного коэффициента, а затем вызывает invalidate () и requestLayout (). На данный момент я не совсем уверен, если invalidate () необходимо, но в любом случае - вот оно:
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector){
mScaleFactor *= detector.getScaleFactor();
// Apply limits to the zoom scale factor:
mScaleFactor = Math.max(0.6f, Math.min(mScaleFactor, 1.5f);
invalidate();
requestLayout();
return true;
}
}
Следующее, что я должен был сделать в своем ZoomableRelativeLayout, это переопределить onLayout (). Для этого мне было полезно взглянуть на попытки других людей в масштабируемом макете, а также я нашел очень полезным взглянуть на оригинальный исходный код Android для RelativeLayout. Мой переопределенный метод копирует большую часть того, что есть в onLayout () RelativeLayout, но с некоторыми изменениями.
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
int count = getChildCount();
for(int i=0;i<count;i++){
View child = getChildAt(i);
if(child.getVisibility()!=GONE){
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)child.getLayoutParams();
child.layout(
(int)(params.leftMargin * mScaleFactor),
(int)(params.topMargin * mScaleFactor),
(int)((params.leftMargin + child.getMeasuredWidth()) * mScaleFactor),
(int)((params.topMargin + child.getMeasuredHeight()) * mScaleFactor)
);
}
}
}
Здесь важно то, что при вызове layout () для всех дочерних элементов я применяю масштабный коэффициент к параметрам макета, а также для этих дочерних элементов. Это один шаг к решению проблемы отсечения, а также важно правильно установить положение x, y дочерних элементов относительно друг друга для различных масштабных коэффициентов.
Еще одним ключевым моментом является то, что я больше не пытаюсь масштабировать Canvas в dispatchDraw (). Вместо этого каждый дочерний View масштабирует свой Canvas после получения коэффициента масштабирования из родительского ZoomableRelativeLayout с помощью метода get.
Далее я перейду к тому, что я должен был сделать в дочерних представлениях моего ZoomableRelativeLayout. В моем ZoomableRelativeLayout есть только один тип View, который я использую как дочерний элемент; это представление для рисования графики SVG, которое я называю SVGView. Конечно, материал SVG здесь не актуален. Вот его метод onMeasure ():
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
float parentScale = ((FloorPlanLayout)getParent()).getScaleFactor();
int chosenWidth, chosenHeight;
if( parentScale > 1.0f){
chosenWidth = (int) ( parentScale * (float)svgImage.getDocumentWidth() );
chosenHeight = (int) ( parentScale * (float)svgImage.getDocumentHeight() );
}
else{
chosenWidth = (int) ( (float)svgImage.getDocumentWidth() );
chosenHeight = (int) ( (float)svgImage.getDocumentHeight() );
}
setMeasuredDimension(chosenWidth, chosenHeight);
}
и onDraw ():
@Override
protected void onDraw(Canvas canvas){
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(((FloorPlanLayout)getParent()).getScaleFactor(),
((FloorPlanLayout)getParent()).getScaleFactor());
if( null==bm || bm.isRecycled() ){
bm = Bitmap.createBitmap(
getMeasuredWidth(),
getMeasuredHeight(),
Bitmap.Config.ARGB_8888);
... Canvas draw operations go here ...
}
Paint drawPaint = new Paint();
drawPaint.setAntiAlias(true);
drawPaint.setFilterBitmap(true);
// Check again that bm isn't null, because sometimes we seem to get
// android.graphics.Canvas.throwIfRecycled exception sometimes even though bitmap should
// have been drawn above. I'm guessing at the moment that this *might* happen when zooming into
// the house layout quite far and the system decides not to draw anything to the bitmap because
// a particular child View is out of viewing / clipping bounds - not sure.
if( bm != null){
canvas.drawBitmap(bm, 0f, 0f, drawPaint );
}
canvas.restore();
}
Опять же - в качестве заявления об ограничении ответственности, вероятно, есть некоторые бородавки в том, что я разместил там, и мне еще предстоит тщательно изучить предложения hackbod и включить их. Я намерен вернуться и отредактировать это дальше. Тем временем, я надеюсь, что он может начать предоставлять полезные советы другим о том, как реализовать масштабируемую ViewGroup.