Please help, I'm trying to implement the same transitions.
1)https://storage.googleapis.com/spec-host-backup/mio-design%2Fassets%2F1tAlSW8Kp7JlXJNo16cv6RZqUl1iNsjen%2Fcards-transition.mp4
2)https://storage.googleapis.com/spec-host-backup/mio-design%2Fassets%2F1qIHOMquJE7flVh1ttDTSogXdvEX2lY_1%2F01-list-parentchild.mp4
But I don`t know how can I do it.
You need put this parameter into imageview of the first activity (xml):
android:transitionName="your_transaction_name"
And when you want open the other activity:
ImageView imageView = findViewById(R.id.your_image_id);
Pair pair = new Pair<>(imageView, ViewCompat.getTransitionName(imageView));
ActivityOptionsCompat transitionActivityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(this, pair);
Intent intent = new Intent(this, YourOtherActivity.class);
ActivityCompat.startActivityForResult(this, intent, 0, transitionActivityOptions.toBundle());
Add on your OtherActivity the same parameter to your imageView:
android:transitionName="your_transaction_name"
Note: It works only with android API > 21
The transition in the gif on the left side transitions the list element into the content area of the second activity (Toolbar stays in place). In the gif on the right side, the transition transforms the list element into the complete screen of the second activity. The following code provides the effect in the left gif. However, it should be possible to adapt the solution with minor modifications to achieve the transition in the right gif.
Note this only works on Lollipop. However, it is possible to mock a different effect on older devices. Furthermore, the sole purpose of the provided code is to show how it could be done. Don't use this directly in your app.
MainActivity:
public class MainActivity extends AppCompatActivity {
MyAdapter myAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
ListView listView = (ListView) findViewById(R.id.list_view);
myAdapter = new MyAdapter(this, 0, DataSet.get());
listView.setAdapter(myAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, final View view, final int position, long id) {
startTransition(view, myAdapter.getItem(position));
}
});
}
private void startTransition(View view, Element element) {
Intent i = new Intent(MainActivity.this, DetailActivity.class);
i.putExtra("ITEM_ID", element.getId());
Pair<View, String>[] transitionPairs = new Pair[4];
transitionPairs[0] = Pair.create(findViewById(R.id.toolbar), "toolbar"); // Transition the Toolbar
transitionPairs[1] = Pair.create(view, "content_area"); // Transition the content_area (This will be the content area on the detail screen)
// We also want to transition the status and navigation bar barckground. Otherwise they will flicker
transitionPairs[2] = Pair.create(findViewById(android.R.id.statusBarBackground), Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME);
transitionPairs[3] = Pair.create(findViewById(android.R.id.navigationBarBackground), Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME);
Bundle b = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this, transitionPairs).toBundle();
ActivityCompat.startActivity(MainActivity.this, i, b);
}
}
activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/colorPrimary"
android:transitionName="toolbar" />
<ListView
android:id="#+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
DetailActivity:
public class DetailActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
long elementId = getIntent().getLongExtra("ITEM_ID", -1);
Element element = DataSet.find(elementId);
((TextView) findViewById(R.id.title)).setText(element.getTitle());
((TextView) findViewById(R.id.description)).setText(element.getDescription());
// if we transition the status and navigation bar we have to wait till everything is available
TransitionHelper.fixSharedElementTransitionForStatusAndNavigationBar(this);
// set a custom shared element enter transition
TransitionHelper.setSharedElementEnterTransition(this, R.transition.detail_activity_shared_element_enter_transition);
}
}
activity_detail.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/colorPrimary"
android:transitionName="toolbar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#abc"
android:orientation="vertical"
android:paddingBottom="200dp"
android:transitionName="content_area"
android:elevation="10dp">
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
detail_activity_shared_element_enter_transition.xml (/res/transition/):
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together">
<changeBounds/>
<changeTransform/>
<changeClipBounds/>
<changeImageTransform/>
<transition class="my.application.transitions.ElevationTransition"/>
</transitionSet>
my.application.transitions.ElevationTransition:
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class ElevationTransition extends Transition {
private static final String PROPNAME_ELEVATION = "my.elevation:transition:elevation";
public ElevationTransition() {
}
public ElevationTransition(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
#Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
private void captureValues(TransitionValues transitionValues) {
Float elevation = transitionValues.view.getElevation();
transitionValues.values.put(PROPNAME_ELEVATION, elevation);
}
#Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
Float startVal = (Float) startValues.values.get(PROPNAME_ELEVATION);
Float endVal = (Float) endValues.values.get(PROPNAME_ELEVATION);
if (startVal == null || endVal == null || startVal.floatValue() == endVal.floatValue()) {
return null;
}
final View view = endValues.view;
ValueAnimator a = ValueAnimator.ofFloat(startVal, endVal);
a.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setElevation((float)animation.getAnimatedValue());
}
});
return a;
}
}
TransitionHelper:
public class TransitionHelper {
public static void fixSharedElementTransitionForStatusAndNavigationBar(final Activity activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
return;
final View decor = activity.getWindow().getDecorView();
if (decor == null)
return;
activity.postponeEnterTransition();
decor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public boolean onPreDraw() {
decor.getViewTreeObserver().removeOnPreDrawListener(this);
activity.startPostponedEnterTransition();
return true;
}
});
}
public static void setSharedElementEnterTransition(final Activity activity, int transition) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
return;
activity.getWindow().setSharedElementEnterTransition(TransitionInflater.from(activity).inflateTransition(transition));
}
}
So what are the different parts here: We have two activities. During the transition, four views are transitioned between the activities.
Toolbar: like in the left gif the toolbar doesn't move with the rest of the content.
ListView element View -> becomes the content view of the DetailActivity
StatusBar and NavigationBar Background: If we don't add these views to the set of transitioned views they will fade out and back in during the transition. This however requires to delay the enter transition (see: TransitionHelper.fixSharedElementTransitionForStatusAndNavigationBar)
In the MainActivity the transitioned views are added to the Bundle that is used to start the DetailActivity. Furthermore the transitioned views need to be named (transitionName) in both activities. This can be done in the layout xml as well as programatically.
The default set of transitions, that is used during the shared element transition, affects different aspects of the view(for example: view bounds - see 2). However differences in the elevation of a view are not animated. This is why the presented solution utilizes the custom ElevationTransition.
you can also see this from google documentation:
https://developer.android.com/training/transitions/start-activity
Related
So this is my code. I am trying to create what looks like a PPT slideshow of images with their title and description in my application.
I have the below code and I am stuck on how to allow the user to:
When he gets into the slideshow he would get the images, with a title and text "description" for each.
when "ENTER" is pressed set the text on that image to invisible.
When the user navigates to the right, the next view(s) would contain a text and Image.
-When the user navigates back left the text view that he set invisible should still be invisible and the has the option to get it to become visible again by just pressing enter.
The last part is what I am stuck on, as I am not able to figure out how each "Linear Layout" inside my viewpager would remember its visibility and act accordingly. (i.e. gets back to visible once it was invisible and vice versa).
I appreciate your input.
Please find the code below:
My ViewPager XML file:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/fragment_pager_main_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/default_background"
tools:context=".PowerPointActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/fragment_pager_viewPager2"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
My views XML file:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/fragment_pager_item_parent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/fragment_pager_item_imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:scaleType="fitXY"/>
<LinearLayout
android:id="#+id/fragment_pager_item_main_child"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="textStart"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="#+id/fragment_pager_item_parent"
app:layout_constraintStart_toStartOf="#+id/fragment_pager_item_parent">
<!-- the linear layout below was not initially here but I was trying to have a layout for each child.-->
<LinearLayout
android:id="#+id/fragment_pager_item_child"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="textStart"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="#+id/fragment_pager_item_parent"
app:layout_constraintStart_toStartOf="#+id/fragment_pager_item_parent">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/fragment_pager_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:gravity="top"
android:textColor="#FFFFFF"
android:textSize="12dp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="#+id/fragment_pager_item_child"
app:layout_constraintTop_toTopOf="#+id/fragment_pager_item_child"
app:layout_constraintVertical_bias="0.0"
tools:text="Topic" />
<TextView
android:id="#+id/fragment_pager_item_about"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="top"
android:layout_marginStart="10dp"
android:elegantTextHeight="true"
android:text="#string/about_topic"
android:textColor="#ffffff"
android:textSize="12sp"
app:layout_constraintHorizontal_bias="10"
app:layout_constraintStart_toStartOf="#+id/fragment_pager_item_title"
app:layout_constraintTop_toBottomOf="#id/fragment_pager_item_title"
app:layout_dodgeInsetEdges="left" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
My Adapter Class:
public class PowerpointAdapter extends RecyclerView.Adapter{
private List<Image> mPowerPointList;
static class PagesViewHolder extends RecyclerView.ViewHolder {
private ImageView mViewImageView;
private TextView mTitleView;
private TextView mDescriptionView;
private LinearLayout mLinearLayout;
public PagesViewHolder(#NonNull View itemView) {
super(itemView);
Timber.i("PagesViewHolder");
mViewImageView = itemView.findViewById(R.id.fragment_pager_item_imageView);
mTitleView = itemView.findViewById((R.id.fragment_pager_item_title));
mDescriptionView = itemView.findViewById(R.id.fragment_pager_item_about);
mLinearLayout = itemView.findViewById(R.id.fragment_pager_item_child);
}
}
public PowerpointAdapter(List<Image> mPowerPointList) { this.mPowerPointList = mPowerPointList;}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
Timber.i("onCreateViewHolder");
View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_pager_item, parent, false);
return new PagesViewHolder(mView);
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
Timber.i("onBindViewHolder");
PagesViewHolder mViewHolder = (PagesViewHolder) holder;
Image powerpoint = mPowerPointList.get(position);
mViewHolder.mViewImageView.setImageResource(powerpoint.getTrialImage());
mViewHolder.mTitleView.setText(powerpoint.getTitle());
mViewHolder.mDescriptionView.setText(powerpoint.getDescription());
mViewHolder.mLinearLayout.setVisibility(View.VISIBLE);
}
#Override
public int getItemCount() {
Timber.i("getItemCount");
return mPowerPointList.size();}
My activity class:
// for a one image display, the image can be treated similarly to a powerpoint.
public class PowerPointActivity extends Activity {
private ViewPager2 viewPager2;
#Override
protected void onCreate(Bundle savedInstanceState) {
Timber.i("onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_pager_main);
ConstraintLayout mLayout = findViewById(R.id.fragment_pager_main_parent);
mLayout.setBackgroundResource(R.color.Transparent_black);
viewPager2 = findViewById(R.id.fragment_pager_viewPager2);
setUpPagerAdapter();
}
/**
* this method will set up the adapter
*/
private void setUpPagerAdapter() {
Timber.i("SetUpPagerAdapter");
PowerpointAdapter pagerAdapter = new PowerpointAdapter(fetchDummyData());
viewPager2.setAdapter(pagerAdapter);
viewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
}
/**
* #return this method will return dummy data in form of list
*/
private List<Image> fetchDummyData() {
Timber.i("fetchDummyData in Power Point Activity");
List<Image> powerpointList = new ArrayList<>();
String[] dummyArrDescriptions = getResources().getStringArray(R.array.array_str_descriptions_for_powerPoints);
String[] dummyArrTitles = getResources().getStringArray(R.array.array_str_titles_for_powerPoints);
for (int index = 0; index < dummyArrTitles.length; ++index) {
Image image = new Image(dummyArrTitles[index], dummyArrDescriptions[index], R.drawable.ppt_image);
powerpointList.add(image);
}
return powerpointList;
}
/**
* this method handles slideshow navigation
*/
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Timber.i("OnKeyDown");
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_LEFT: {
viewPager2.setCurrentItem(viewPager2.getCurrentItem() - 1);
Toast.makeText(PowerPointActivity.this, "Left pressed!", Toast.LENGTH_SHORT).show();
break;
}
case KeyEvent.KEYCODE_DPAD_RIGHT: {
viewPager2.setCurrentItem(viewPager2.getCurrentItem() + 1);
Toast.makeText(PowerPointActivity.this, "Right pressed!", Toast.LENGTH_SHORT).show();
break;
}
case KeyEvent.KEYCODE_BACK: {
Toast.makeText(PowerPointActivity.this, "Back pressed!", Toast.LENGTH_SHORT).show();
super.onBackPressed();
break;
}
// todo: Fix the visibility of each page description
case KeyEvent.KEYCODE_ENTER: {
if (findViewById(R.id.fragment_pager_item_child).isShown()) {
Timber.i("It is shown. Setting it to not shown! "+ viewPager2.getCurrentItem() + " now.");
// here this specific child's text should be set to invisible and remembered. findViewById(R.id.fragment_pager_item_child).setVisibility(View.INVISIBLE);
LinearLayout mLayout = findViewById(R.id.fragment_pager_item_child);
Animation outFade = AnimationUtils.loadAnimation(getApplicationContext(),R.anim.fade_out);
mLayout.startAnimation(outFade);
mLayout.startAnimation(outFade);
break;
} else {
Timber.i("It is now not shown. Setting it to shown! " + viewPager2.getCurrentItem() + " now.");
LinearLayout mLayout = findViewById(R.id.fragment_pager_item_child);
Animation aniFade = AnimationUtils.loadAnimation(getApplicationContext(),R.anim.fade_in);
mLayout.startAnimation(aniFade);
mLayout.startAnimation(aniFade);
findViewById(R.id.fragment_pager_item_child).setVisibility(View.VISIBLE);
break;
}
}
}
return false;
}
}
Thank you!
From the Android documentation we can find that the Adapter holds the following responsibilities:
Acts as a bridge between an AdapterView and the underlying data for that view.
Provides access to the data items
Responsible for making a View for each item in the data set.
So basically it's not the View's job to remember the child's visibility and act upon it. It's PowerpointAdapter responsibility.
You need a status field in your Image object and change it when the view visibility is changed. Also, don't forget to set the view visibility according to this status variable.
For example:
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
Timber.i("onBindViewHolder");
PagesViewHolder mViewHolder = (PagesViewHolder) holder;
Image powerpoint = mPowerPointList.get(position);
mViewHolder.mViewImageView.setImageResource(powerpoint.getTrialImage());
mViewHolder.mTitleView.setText(powerpoint.getTitle());
mViewHolder.mDescriptionView.setText(powerpoint.getDescription());
if(powerpoint.isLayoutVisibile){
mViewHolder.mLinearLayout.setVisibility(View.VISIBLE);
}else{
mViewHolder.mLinearLayout.setVisibility(View.GONE);
}
}
And Inside your ClickListener you should change this field and notify the PowerpointAdapter of these changes.
I'm having problems with android transition. I have a recyclerview list with multiple elements. The animation should start from any row's image when clicked, but it doesn't, it starts from the middle of the row.
I have a fragment with a RecyclerView, here is where the transition begins
fragment_states:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="namespace.fragments.StatesFragment">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerViewStates"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?attr/actionBarSize"
/>
</FrameLayout>
The previous recyclerView has a list adapter where a row is defined as shown below. Here I defined android:transitionName="stateImage", is the image from where the transition should start.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/imgState"
android:layout_width="60dp"
android:layout_height="60dp"
android:transitionName="stateImage"
android:padding="6dp" />
<TextView
android:padding="10dp"
android:layout_toRightOf="#id/imgState"
android:id="#+id/txtNombreEstado"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:textAppearance="#style/Base.TextAppearance.AppCompat.Medium"
/>
</RelativeLayout>
Here's how I call the transition:
StatesFragment.java
public class StatesFragment extends Fragment {
...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
recyclerView.addOnItemTouchListener(new Helper.RecyclerTouchListener(getActivity(), recyclerView, new Helper.ClickListener() {
#Override
public void onClick(View view, int position) {
try {
Intent intent = new Intent(getActivity(), CitiesActivity.class);
//Check if we're running on Android 5.0 or higher
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Call some material design APIs here
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(getActivity(), view, "stateImage");
startActivity(intent, options.toBundle());
} else {
// Implement this feature without material design
startActivity(intent);
}
}
catch (Exception ex)
{
}
}
#Override
public void onLongClick(View view, int position) {
}
}));
}
Any idead on what I'm doing wrong?
My mistake was in this line:
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(getActivity(), view, "stateImage");
I was passing the view, in this case the row. This is how I fixed it
final ImageView image = (ImageView)
view.findViewById(R.id.stateImage);
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(getActivity(), image, "stateImage");
I have 4 fragments and I want to create a sort of vertical viewpager but I need to keep visible a view of the previous page.
In more details:
Fragment A have a TextView (TV1) on the bottom and other views.
Fragment B have a TextView (TV2) on the bottom and other views.
Fragment C have a TextView (TV3) on the bottom and other views.
I start my Activity, Fragment A occupies the entire layout.
Click on a button -> Fragment A slides up and Fragment B appears but TV1 should still be visibile and fixed on the top of the screen.
Click on a button -> Fragment B slides up and Fragment C appears but TV2 should still be visibile and fixed on the top of the screen (TV2 should replace TV1)...
If I click on TV2 the Fragment B will reappear above the Fragment B.
How can I obtain this behavior?
I finally managed to implement something similar to what you ask about. Here's how it looks:
It might a bit hacky, though that's how I archive it:
First, I needed some TvFragment:
public class TvFragment extends android.support.v4.app.Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.tv_fragment, container, false);
TextView textView = (TextView)rootView.findViewById(R.id.tvTextView);
textView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
((OnScrollChanged)getActivity()).onScroll(TvFragment.this);
}
});
return rootView;
}
public void display(int height, String tvTitle, int backgroundColor) {
if (getView() == null) {
return;
}
ViewGroup.LayoutParams params = getView().getLayoutParams();
params.height = height;
getView().setLayoutParams(params);
TextView textView = (TextView)getView().findViewById(R.id.tvTextView);
textView.setText(tvTitle);
getView().setBackgroundColor(backgroundColor);
}
}
And it's tv_fragment.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_gravity="bottom"
android:id="#+id/tvTextView"
android:gravity="center"
android:textColor="#FFFFFF"
android:textSize="24sp"
android:background="#drawable/textview_backgroud_selector"
android:padding="8dp"
android:layout_margin="#dimen/tv_button_margin"
android:layout_width="match_parent"
android:layout_height="#dimen/tv_button_height" />
</FrameLayout>
And then we need to fill the Activity with our Fragment
Then, we need to have an adapter to fill it with our fragments:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<fragment
android:id="#+id/fragmentA"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:name="klogi.com.verticalpagination.TvFragment"/>
<fragment
android:id="#+id/fragmentB"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:name="klogi.com.verticalpagination.TvFragment"/>
<fragment
android:id="#+id/fragmentC"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:name="klogi.com.verticalpagination.TvFragment"/>
</LinearLayout>
</ScrollView>
I.e. we keep all three fragments in one ScrollView.
Small helper interface to communicate between fragment and activity:
public interface OnScrollChanged {
void onScroll(Fragment fragment);
}
And last piece is MainActivity class:
public class MainActivity extends AppCompatActivity implements OnScrollChanged {
TvFragment fragmentA;
TvFragment fragmentB;
TvFragment fragmentC;
int bigFragmentHeight;
int smallFragmentHeight;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportActionBar().hide();
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
bigFragmentHeight = metrics.heightPixels - getStatusBarHeight();
smallFragmentHeight = bigFragmentHeight - getResources().getDimensionPixelSize(R.dimen.tv_button_height) - 2 * getResources().getDimensionPixelSize(R.dimen.tv_button_margin);
fragmentA = (TvFragment)getSupportFragmentManager().findFragmentById(R.id.fragmentA);
fragmentA.display(bigFragmentHeight, "TV1", Color.BLUE);
fragmentB = (TvFragment)getSupportFragmentManager().findFragmentById(R.id.fragmentB);
fragmentB.display(smallFragmentHeight, "TV2", Color.RED);
fragmentC = (TvFragment)getSupportFragmentManager().findFragmentById(R.id.fragmentC);
fragmentC.display(smallFragmentHeight, "TV3", Color.YELLOW);
ScrollView scrollView = (ScrollView)findViewById(R.id.scrollView);
scrollView.setOnTouchListener( new View.OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
}
public int getStatusBarHeight() {
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
#Override
public void onScroll(Fragment fragment) {
ScrollView scrollView = (ScrollView)findViewById(R.id.scrollView);
int currentScroll = scrollView.getScrollY();
if (fragment.equals(fragmentA)) {
if (currentScroll == 0) {
scrollView.smoothScrollTo(0, smallFragmentHeight);
} else {
scrollView.smoothScrollTo(0, 0);
}
} else if (fragment.equals(fragmentB)) {
if (currentScroll == smallFragmentHeight) {
scrollView.smoothScrollTo(0, smallFragmentHeight + bigFragmentHeight);
} else {
scrollView.smoothScrollTo(0, smallFragmentHeight);
}
} else if (fragment.equals(fragmentC)) {
// do nothing
}
}
}
What am I doing here - is to disabling "normal" scrolling of the ScrollView and depends on which fragment's button has been clicked - smooth scrolling up or down.
I used also this resources:
dimens:
<resources>
<dimen name="tv_button_height">48dp</dimen>
<dimen name="tv_button_margin">8dp</dimen>
</resources>
colors:
<resources>
<color name="textview_backgroud">#AAAAAA</color>
<color name="textview_backgroud_pressed">#777777</color>
</resources>
and textview_backgroud_selector:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#color/textview_backgroud_pressed"/>
<item android:color="#color/textview_backgroud"/>
</selector>
I've uploaded the complete project into my dropbox - feel free to check it out
That's it! I hope, it helps
Having a text displayed initially at screen bottom. When clicking on it, the list view below it should sliding up. Clicking on the text again the list view sliding down.
Made it work with the snippet below except the first time clicking on the text the list does not do the animation sliding up. After that it will animate sliding up/down as expected.
It is because in the first clicking and call of showListView(true); since the list view’s visibility was “gone”, so it has not had height. The “y” == 0 translate does not do anything. It is just the list view’s visibility change to “visible”, which push the titleRow change its position.
But if to begin with the list view’s visibility to “visible”, the initial showListView(false); in setupListViewAdapter() does not push the list view down to initial state (outside of screen bottom) since it does not have the height until the list row are filled in from the adapter by mListView.setAdapter(mListAdapter).
Is there better way to do sidling up/down the list view?
<TextView
android:id="#+id/titleRow”
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=“title row”
/>
<ListView
android:id="#+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility=“gone”
>
</ListView>
initial state list view collapsed(outside screen bottom)
+++++++++++++++++++++++++++
+ mTitleRow + ^ +
+++++ screen bottom +++++
listview expanded
+++++++++++++++++++++++++++
+ mTitleRow + ^ +
+++++++++++++++++++++++++++
+ +
+ mListView +
+ +
+++++ screen bottom +++++
void setupListViewAdapter(){
mTitleRow.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mTitleRow.getTag() == STATE_COLLAPSED)
{
mListView.setVisibility(View.VISIBLE);
showListView(true);
} else {
showListView(false);
}
}
});
mListView.setAdapter(mListAdapter);
showListView(false);
}
private static final int STATE_EXPANDED = 1;
private static final int STATE_COLLAPSED = 2;
boolean mListInAnimation = false;
public void showListView(final boolean show) {
if (mListView != null && !mListInAnimation) {
mListInAnimation = true;
mTitleRow.setTag(show ? STATE_EXPANDED : STATE_COLLAPSED);
int translateY = show ? 0 : (listHeight);
mTitleRow.animate().translationY(translateY).setDuration(300).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
mListInAnimation = false;
}
});
}
}
You can do this very easily using API 16's LayoutTransition.CHANGING.
Set animateLayoutChanges to true on the parent ViewGroup:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/parent"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
android:animateLayoutChanges="true">
<TextView
android:id="#+id/titleRow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="title row"/>
<ListView
android:id="#+id/list_view"
android:layout_width="match_parent"
android:layout_height="0dp"/>
</LinearLayout>
Enable LayoutTransition.CHANGING; on click of the title, set the height of the list view:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.parent);
linearLayout.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
final View listView = findViewById(R.id.list_view);
View titleRow = findViewById(R.id.titleRow);
titleRow.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = params.height == 0 ? ViewGroup.LayoutParams.WRAP_CONTENT : 0;
listView.setLayoutParams(params);
}
});
}
I'm using android.app.AlertDialog that contains a ScrollView and inside (of course) some content.
Google shows in its material-guidelines a small grey line above the buttons when the content is larger than the visible space: http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior
My alert-dialog doesn't have this grey line. How do I create this line?
I already tried a background for the ScrollView like this:
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#color/dark_transparent"/>
</shape>
But this created a line on top AND bottom. And it also appears when the content is smaller than the visible space, which looks ugly.
I found a solution for the grey line! :)
I found the solution how to show the grey line at all here: How to make a static button under a ScrollView?
For the check if I want to show it, I found the solution here: How can you tell when a layout has been drawn?
This is how my code looks like now:
This is my_material_dialog.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ScrollView
android:id="#+id/myMaterialDialog_scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true">
<LinearLayout
android:id="#+id/myMaterialDialog_textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="5dp"
android:paddingLeft="26dp"
android:paddingRight="26dp"
android:paddingTop="15dp">
<!-- dynamically added content goes here -->
</LinearLayout>
</ScrollView>
<View
android:id="#+id/myMaterialDialog_lineView"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_gravity="center_horizontal"
android:background="#15000000"
android:gravity="center_horizontal"
android:visibility="gone"/>
</LinearLayout>
And this is MyMaterialDialog.java:
public class MyMaterialDialog extends AlertDialog {
private Context context;
private ScrollView scrollView;
private LinearLayout textView;
private View lineView;
private boolean checkingLayout;
public MyMaterialDialog(final Context context) {
super(context);
this.context = context;
final View myMaterialDialog = getLayoutInflater().inflate(R.layout.my_material_dialog, null);
this.scrollView = (ScrollView) myMaterialDialog.findViewById(R.id.myMaterialDialog_scrollView);
this.textView = (LinearLayout) myMaterialDialog.findViewById(R.id.myMaterialDialog_textView);
this.lineView = myMaterialDialog.findViewById(R.id.myMaterialDialog_lineView);
final ViewTreeObserver vto = scrollView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (checkingLayout) {
// avoid infinite recursions
return;
}
checkingLayout = true;
if (scrollView.canScrollVertically(1)) {
lineView.setVisibility(View.VISIBLE);
} else {
lineView.setVisibility(View.GONE);
}
checkingLayout = false;
}
});
setTitle(R.string.myMaterialDialog_title);
setText();
setView(myMaterialDialog);
show();
}
/**
* do request to webserver for texts
*/
private final void setText() {
final GetDialogTextRequest request = new GetDialogTextRequest();
final GetDialogTextResultHandler resultHandler = new GetDialogTextResultHandler(context, textView);
request.submit(resultHandler);
}
}
private final class GetDialogTextResultHandler extends DefaultRequestResultHandler<List<MyTextObject>> {
private final Context context;
private final LinearLayout textView;
private GetDialogTextResultHandler(final Context context, final LinearLayout textView) {
super(context);
this.context = context;
this.textView = textView;
}
#Override
public void handleResult(final List<MyTextObject> texts) {
setText(texts); // ... sets the content, can vary in size
}
}
Add something like this below your ScrollView:
<View android:layout_width="fill_parent"
android:layout_height="2px"
android:background="#90909090"/>
It should give you a slim greyish horizontal bar.
If you're using API 23+ (Android 6.0) using the following in scroll view will add the top and bottom indicators.
android:scrollIndicators="top|bottom"
If targeting older API's I looked into Google's Alert Dialog controller source code, and am using the following code:
private static void setScrollIndicators(ViewGroup root, final NestedScrollView content,
final int indicators, final int mask) {
// use it like this:
// setScrollIndicators(contentPanel, content, indicators,
// ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_BOTTOM);
// Set up scroll indicators (if present).
View indicatorUp = root.findViewById(R.id.scrollIndicatorUp);
View indicatorDown = root.findViewById(R.id.scrollIndicatorDown);
if (Build.VERSION.SDK_INT >= 23) {
// We're on Marshmallow so can rely on the View APIsaa
ViewCompat.setScrollIndicators(content, indicators, mask);
// We can also remove the compat indicator views
if (indicatorUp != null) {
root.removeView(indicatorUp);
}
if (indicatorDown != null) {
root.removeView(indicatorDown);
}
} else {
// First, remove the indicator views if we're not set to use them
if (indicatorUp != null && (indicators & ViewCompat.SCROLL_INDICATOR_TOP) == 0) {
root.removeView(indicatorUp);
indicatorUp = null;
}
if (indicatorDown != null && (indicators & ViewCompat.SCROLL_INDICATOR_BOTTOM) == 0) {
root.removeView(indicatorDown);
indicatorDown = null;
}
if (indicatorUp != null || indicatorDown != null) {
final View top = indicatorUp;
final View bottom = indicatorDown;
if (content != null) {
// We're just showing the ScrollView, set up listener.
content.setOnScrollChangeListener(
new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX,
int scrollY,
int oldScrollX, int oldScrollY) {
manageScrollIndicators(v, top, bottom);
}
});
// Set up the indicators following layout.
content.post(new Runnable() {
#Override
public void run() {
manageScrollIndicators(content, top, bottom);
}
});
} else {
// We don't have any content to scroll, remove the indicators.
if (top != null) {
root.removeView(top);
}
if (bottom != null) {
root.removeView(bottom);
}
}
}
}
}
private static void manageScrollIndicators(View v, View upIndicator, View downIndicator) {
if (upIndicator != null) {
upIndicator.setVisibility(
ViewCompat.canScrollVertically(v, -1) ? View.VISIBLE : View.INVISIBLE);
}
if (downIndicator != null) {
downIndicator.setVisibility(
ViewCompat.canScrollVertically(v, 1) ? View.VISIBLE : View.INVISIBLE);
}
}
And XML looks like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="#+id/scrollIndicatorUp"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#color/dim_white"
android:visibility="gone"
tools:visibility="visible" />
<android.support.v4.widget.NestedScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<... you content here>
</android.support.v4.widget.NestedScrollView>
<View
android:id="#+id/scrollIndicatorDown"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#color/dim_white"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>