Android - Как позиционировать View off-screen?

Я пытаюсь анимировать простой ImageView в своем приложении, и я хочу, чтобы он скользил в нижней части экрана и приходил в положение покоя, где верхний 50 пикселей представления находится вне верхней части экрана (например, конечная позиция ImageView должна быть -50px в X). Я попытался использовать AbsoluteLayout для этого, но это фактически отсекает верхние 50 пикселей ImageView таким образом, что верхний 50px никогда не отображается. Мне нужно, чтобы верхние 50 пикселей изображения были видны/отображены во время анимации, а затем просто заставили его немного отдохнуть. Надеюсь, я объяснил это достаточно хорошо.

Вот что я сейчас использую в качестве макета и слайд-анимации (это в настоящее время не отображает 50px изображения ImageView):

Разметка:

<?xml version="1.0" encoding="utf-8"?>
   <AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_height="fill_parent" 
      android:layout_width="fill_parent" 
      android:id="@+id/QuickPlayClipLayout">
      <ImageView android:id="@+id/Clip"
         android:background="@drawable/clip" 
         android:layout_width="fill_parent" 
         android:layout_height="wrap_content" 
         android:layout_y="-50dp">
      </ImageView>
   </AbsoluteLayout>

Анимация:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
   <translate android:fromYDelta="100%p" 
       android:toYDelta="0"
       android:duration="1000"/>
   <alpha android:fromAlpha="0.0" 
       android:toAlpha="1.0"
       android:duration="1000" />
</set>

Спасибо заранее.

Ответ 1

Я понял решение, которое должно быть легко реализовано. Это включает в себя модификацию макета и активность, раздувающую макет... см. Ниже:

Активность (QuickPlay.java):

public class QuickPlay extends Activity implements AnimationListener
{
    private ImageView myImageView;
    private LinearLayout LL;

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

        myImageView = (ImageView) this.findViewById(R.id.Clip);
        LL = (LinearLayout) this.findViewById(R.id.QuickPlayClipLayout);

        //finally
        Animation anim = AnimationUtils.loadAnimation(this, R.anim.slide_in_quickplay);
        anim.setAnimationListener(this);
        LL.startAnimation(anim);
    }
    @Override
    public void onAnimationEnd(Animation animation){}

    @Override
    public void onAnimationRepeat(Animation animation){}

    @Override
    public void onAnimationStart(Animation animation)
    {
        // This is the key...
        //set the coordinates for the bounds (left, top, right, bottom) based on the offset value (50px) in a resource XML
        LL.layout(0, -(int)this.getResources().getDimension(R.dimen.quickplay_offset), 
                LL.getWidth(), LL.getHeight() + (int)this.getResources().getDimension(R.dimen.quickplay_offset));
    }
}

Новый LinearLayout (CustomLinearLayout.java):

public class CustomLinearLayout extends LinearLayout
{
    private Context myContext;

    public CustomLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        myContext = context;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec+((int)myContext.getResources().getDimension(R.dimen.quickplay_offset)));
    }
}

Макет (/res/layout/quick_play_screen.xml):

<?xml version="1.0" encoding="utf-8"?>
   <com.games.mygame.CustomLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_height="fill_parent" 
      android:layout_width="fill_parent" 
      android:id="@+id/QuickPlayClipLayout">
      <ImageView android:id="@+id/Clip"
         android:background="@drawable/clip" 
         android:layout_width="fill_parent" 
         android:layout_height="wrap_content">
      </ImageView>
   </com.games.mygame.CustomLinearLayout>

Ресурс (/res/values/constants.xml):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="quickplay_offset">50dp</dimen>
</resources>

Анимация (/res/anim/slide_in_quickplay.xml):

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
   <translate android:fromYDelta="100%p" 
       android:toYDelta="0"
       android:duration="1000"/>
   <alpha android:fromAlpha="0.0" 
       android:toAlpha="1.0"
       android:duration="1000" />
</set>

Теперь программа делает то, что мне нужно. Весь макет начинается с экрана внизу, сползает через 1 секунду и приходит в себя, где верхняя часть макета фактически находится на 50 пикселей сверху экрана (т.е. LL.getTop() = -50), а нижняя часть макета располагается на нижней части экрана (т.е. LL.getBottom() = 530 = 480 + 50).

Ответ 2

Чтобы разместить мой взгляд вне экрана, я использовал следующий код:

View myView = /* view you want to position offscreen */
int amountOffscreen = (int)(myView.getWidth() * 0.8); /* or whatever */
boolean offscreen = /* true or false */


int xOffset = (offscreen) ? amountOffscreen : 0;
RelativeLayout.LayoutParams rlParams = 
    (RelativeLayout.LayoutParams)myView.getLayoutParams();
rlParams.setMargins(-1*xOffset, 0, xOffset, 0);
myView.setLayoutParams(rlParams);

Этот код будет размещать myView вне экрана на величинуOffscreen, которая в этом случае ставит 80% экрана на экран, оставляя на экране только 20%.

Не используйте метод layout() напрямую - Android сделает последующие вызовы, чтобы аннулировать ваше представление по случайным причинам, и только layoutParams сохраняются по недействительным вызовам. Если вам интересно, просмотрите строки от 904 до 912 этого файла, чтобы узнать, почему вам нужно изменить layoutParams.

Ответ 3

Вместо этого мы можем просто дать отрицательные значения layout_margin (Top/left/right/bottom) например: Если вы хотите, чтобы ваше представление было отключено от верхней части экрана, вы можете указать

android:layout_marginTop="-40dp"

Ответ 4

Это легко сделать, если вы перейдете к использованию Canvas; те поддержки, которые снимают экран без каких-либо проблем. Однако это будет сложнее реализовать. Вам нужно будет реализовать пользовательский View и написать собственную анимацию в коде. По сути, это сводится к простой обработке 2D-графики, а не к представлениям с использованием встроенных XML-анимаций. Возможно, есть способ сделать это с помощью XML, но я больше знаком с холстами. Очень хорошее место, чтобы увидеть, как это обрабатывается в коде, - это пример игры Lunar Lander, который поставляется с SDK.

Примерно следующие шаги:

  • Поместите пользовательский вид в файл XML, используя что-то вроде <your.package.AnimatingView>, установив его размер для fill-parent.

  • Затем определите класс AnimatingView, который extends SurfaceView and implements SurfaceHolder.Callback. (Это дает вам доступ к чертежу Canvas сразу, а не с помощью метода invalidate(). Это важно, потому что invalidate() обновляется только тогда, когда поток неактивен, например, в конце цикла.Для реализации вашей анимации, вам нужно немедленно его нарисовать.)

  • Затем вы можете реализовать цикл, который рисует движущееся изображение по экрану. Цикл должен начинаться с рисования всего фона (потому что холст автоматически не стирается), а затем рисует изображение в новой позиции в зависимости от пройденного времени. Например, если вы хотите, чтобы ваша анимация заняла 1 секунду, то вы знаете, что если прошло 200 мс, представление должно было перемещать только 200/1000 или 1/5 пути от начальной позиции до конечной позиции.

Вы можете увидеть некоторые примеры того, что я имею в виду в других моих ответах на вопросы анимации: основной ответ о полезности использования SurfaceView и пример цикла для использования. Примечание: второй вопрос касался поворота, и, следовательно, некоторые из трудностей, о которых я говорил, не будут иметь к вам отношения. Удачи!

Ответ 5

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

Когда это произошло, изображения в дочерних группах viewgroup будут обрезаны во время анимации, но затем будут показаны после анимации.

Проблема: высота группы просмотра была wrap_content. Я решил это, установив высоту на значение, которое растянулось от экрана до начала анимации.