FragmentTransaction animation is working but appea

2019-08-02 20:34发布

问题:

I have managed to get the fragments in my app to move correctly as I want them to, however, there is something inconsistent with the animation.

To be more specific: I have an activity with 2 frames, and 3 fragments. Two are stationary, and the third moves from left to right, covering and uncovering the first 2.

When the mobile fragment moves, it appears momentarily in its final location before sliding as it should from start to finish (using animation resources slide_in_left, slide_in_right, slide_out_left and slide_out_right).

EDIT: my test devices are a 2nd gen nexus 7 and a Samsung galaxy s3, so it shouldn't be a hardware issue. The emulator skips too many frames to notice it.

If I move all 3 fragments together this does not occur.

I have made a simple application to demonstrate this issue, perhaps someone can shed some light on a solution. Here are the relevant files:

MainActivity.java

public class MainActivity extends Activity {

    MainFragment mFragment1;
    MainFragment mFragment2;
    MainFragment mFragment3;
    // variable to hold current frame for mFragment2
    int mFrag2Frame;



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

        if (savedInstanceState != null) {
            mFrag2Frame = savedInstanceState.getInt("frag_2_frame");
        } else {
            mFrag2Frame = R.id.rightFrame;
        }
        // First add mFragment1 and mFragment3 where they will stay.
        // Then add mFragment2 over the top of its current frame
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.add(R.id.leftFrame, mFragment1 = MainFragment.Frag1())
                .add(R.id.rightFrame, mFragment3 = MainFragment.Frag3())
                .add(mFrag2Frame, mFragment2 = MainFragment.Frag2())
                .commit();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putInt("frag_2_frame", mFrag2Frame);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        int id = item.getItemId();
        if (id == R.id.action_slide) {
            slide();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    public void slide() {

        boolean isLeft = mFrag2Frame == R.id.leftFrame;
        FragmentTransaction ft = getFragmentManager().beginTransaction();

        // set animations according to current position of mFragment2
        ft.setCustomAnimations(isLeft ? R.anim.slide_in_left : R.anim.slide_in_right,
                isLeft ? R.anim.slide_out_right : R.anim.slide_out_left);

        // change mFrag2Frame according to its current value
        mFrag2Frame = isLeft ? R.id.rightFrame : R.id.leftFrame;

        ft.remove(mFragment2)
                .add(mFrag2Frame, mFragment2 = MainFragment.Frag2())
                .commit();
    }
}

main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.slidingpanels.MainActivity" >

    <FrameLayout
        android:id="@+id/leftFrame"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" >

    </FrameLayout>

    <FrameLayout
        android:id="@+id/rightFrame"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" >

    </FrameLayout>

</LinearLayout>

MainFragment.java

public class MainFragment extends Fragment {

    int mResourceId = R.layout.frag;
    int mColour;

    public MainFragment() {
    }

    private MainFragment(int colour) {
        mColour = colour;
    }

    public static MainFragment Frag1() {
        return new MainFragment(Color.RED);
    }
    public static MainFragment Frag2() {
        return new MainFragment(Color.GREEN);
    }
    public static MainFragment Frag3() {
        return new MainFragment(Color.BLUE);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        View view = inflater.inflate(mResourceId, container, false);
        view.setBackgroundColor(mColour);
        return view;
    }
}

frag.xml

<?xml version="1.0" encoding="utf-8"?>
<com.example.slidingpanels.SlidingImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/SlidingImageView1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/ic_launcher" >

</com.example.slidingpanels.SlidingImageView>

SlidingImageView.java

public class SlidingImageView extends ImageView {

    public SlidingImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public float getXFraction() {

        int width = this.getWidth();
        return (width == 0) ? 0 : getX() / (float) width;
    }

    public void setXFraction(float xFraction) {

        int width = this.getWidth();
        setX((width > 0) ? (xFraction * width) : 0);
    }
}

and finally, I'll include my slide_in_left.xml

<?xml version="1.0" encoding="utf-8"?>
<set>
    <objectAnimator 
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:propertyName="xFraction" 
        android:valueType="floatType"
        android:valueFrom="-1"
        android:valueTo="0" 
        android:duration="500" />
</set>

回答1:

I've had exactly the same issue...

The flicker/glitch is caused because when setXFraction is first called its before onMeasure has happened and so getWidth() returns 0 meaning for the first drawn frame the fragment is in the un-scrolled position, the next call happens after the onMeasure and its drawn in the correct place as getWidth is now returning valid data.

The solution I've found is to save the X fraction in the view object as a float and then override onMeasure() and re-do the setTranslationX, using mXfract * measureSpec.

Works for me, code snippet below:

public class SlidingFrameLayout extends FrameLayout {

  private static final String TAG = SlidingFrameLayout.class.getSimpleName();

  private float mXfract=0f;
  private float mYfract=0f;

  public SlidingFrameLayout(Context context) {
      super(context);
  }

  public SlidingFrameLayout(Context context, AttributeSet attrs) {
      super(context, attrs);
  }

  public SlidingFrameLayout(Context context, AttributeSet attrs, int defStyle) {
      super(context, attrs, defStyle);
  }

  public void setYFraction(final float fraction) {
      mYfract=fraction;
      float translationY = getHeight() * fraction;
      setTranslationY(translationY);
  }

  public float getYFraction() {
      return mYfract;
  }

  public void setXFraction(final float fraction) {
      mXfract=fraction;
      float translationX = getWidth() * fraction;
      setTranslationX(translationX);
  }

  public float getXFraction() {
      return mXfract;
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      int width = MeasureSpec.getSize(widthMeasureSpec);
      int height = MeasureSpec.getSize(heightMeasureSpec);
      // Correct any translations set before the measure was set
      setTranslationX(mXfract*width);
      setTranslationY(mYfract*height);
  }
}

Edited: To use measurespec for width/height instead of call.