De acordo com a documentação andróide, a transição de elementos compartilhados
determina como as visões que são compartilhadas entre duas atividades
transitam entre estas atividades
. Por exemplo, se duas atividades
têm a mesma imagem em posições e tamanhos diferentes, a transição de elementos compartilhados changeImageTransform traduz e dimensiona a imagem suavemente entre estas atividades
.
Este tópico analisa exemplos e bibliotecas relacionadas a Transições Compartilhadas em andróides. Sinta-se livre para contribuir com mais exemplos, links e bibliotecas.
Implementar Facilmente a Transição Compartilhada em Imagens
Você pode facilmente implementar a transição de elementos compartilhados em imagens usando esta biblioteca conhecida como Transitional ImageView.
Vamos mostrar a você como fazer.
Passo 1 - Instalar a Biblioteca
Primeiro registre o jitpack como um repositório em seu nível de aplicação `build.gradle':
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Em seguida, instale a biblioteca:
implementation 'com.github.mostafaaryan:transitional-imageview:v0.2.2'
Passo 2
Crie Transitional ImageView em seu layout, colando o seguinte código:
<com.mostafaaryan.transitionalimageview.TransitionalImageView
android:id="@+id/transitional_image"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:adjustViewBounds="true"
app:res_id="@drawable/sample_image" />
Passo 3
Agora construa um objeto de TransitionImageObject e configure-o para o TransitionalImageView:
TransitionalImageView transitionalImageView = (TransitionalImageView) findViewById(R.id.transitional_image);
TransitionalImage transitionalImage = new TransitionalImage.Builder()
.duration(500)
.backgroundColor(ContextCompat.getColor(MainActivity.this, R.color.color))
.image(R.drawable.sample_image)
/* or */
.image(bitmap)
.create();
transitionalImageView.setTransitionalImage(transitionalImage);
Exemplo completo
Aqui está um belo exemplo de como utilizar esta biblioteca.
**(a). Sapato.java***
A classe do modelo para definir um único sapato.
public class Shoe {
private String Title;
private String imageUrl;
public Shoe(String title, String imageUrl) {
Title = title;
this.imageUrl = imageUrl;
}
public String getTitle() {
return Title;
}
public String getImageUrl() {
return imageUrl;
}
}
**(b). ShoeAdapter.java***
Em seguida, o adaptador reciclador.
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.ariannejad.mostafa.transitional_imageview_implementation.R;
import com.ariannejad.mostafa.transitional_imageview_implementation.controller.MainActivity;
import com.ariannejad.mostafa.transitional_imageview_implementation.model.Shoe;
import com.mostafaaryan.transitionalimageview.TransitionalImageView;
import com.mostafaaryan.transitionalimageview.model.TransitionalImage;
import com.squareup.picasso.Picasso;
import java.io.IOException;
import java.util.ArrayList;
/**
* Created by Mostafa Aryan Nejad on 8/11/17.
*/
public class ShoeAdapter extends RecyclerView.Adapter<ShoeAdapter.ViewHolder> {
Context mContext;
ArrayList<Shoe> shoes = new ArrayList<>();
public ShoeAdapter(Context context, ArrayList<Shoe> shoes) {
mContext = context;
this.shoes = shoes;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_shoe, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
final Shoe shoe = shoes.get(position);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
try{
final Bitmap bitmap = Picasso.with(mContext).load(shoe.getImageUrl()).get();
((Activity) mContext).runOnUiThread(new Runnable() {
@Override
public void run() {
TransitionalImage transitionalImage = new TransitionalImage.Builder()
.duration(500)
/*.backgroundColor(ContextCompat.getColor(, R.color.colorAccent))*/
/*.image(R.drawable.sample_image)*/
.image(bitmap)
.create();
holder.image.setTransitionalImage(transitionalImage);
bitmap.recycle();
}
});
} catch (IOException e){e.printStackTrace();}
}
});
holder.title.setText(shoe.getTitle());
holder.sizes.setText("37,38,39,40");
}
@Override
public int getItemCount() {
return shoes.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView title;
public TextView sizes;
public TransitionalImageView image;
public ViewHolder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.shoe_title);
sizes = (TextView) itemView.findViewById(R.id.shoe_sizes);
image = (TransitionalImageView) itemView.findViewById(R.id.shoe_image);
}
}
}
**(c). ShoeListActivity.java***
A lista de sapatos atividade
:
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CollapsingToolbarLayout;
import android.support.design.widget.TabLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import com.ariannejad.mostafa.transitional_imageview_implementation.R;
import com.ariannejad.mostafa.transitional_imageview_implementation.adapter.ShoeAdapter;
import com.ariannejad.mostafa.transitional_imageview_implementation.model.Shoe;
import java.util.ArrayList;
public class ShoeListActivity extends AppCompatActivity {
private RecyclerView shoeRecyclerView;
private ArrayList<Shoe> shoes = new ArrayList<>();
private ActionBar actionBar;
private AppBarLayout appBarLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_shoe_list);
shoeRecyclerView = (RecyclerView) findViewById(R.id.shoe_recycler_view);
CollapsingToolbarLayout collapsingToolbar =
(CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
appBarLayout = (AppBarLayout) findViewById(R.id.app_bar_layout);
setOnOffsetChangedListener();
collapsingToolbar.setTitleEnabled(false);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
actionBar = getSupportActionBar();
if(actionBar != null) actionBar.setTitle("");
populateList();
}
private void populateList() {
shoes.add(new Shoe("Skechers Relaxed Fit Empire Game On Walking Shoe",
"https://www.shoes.com/pm/skech/skech800828_42965_hd2.jpg"));
shoes.add(new Shoe("Skechers After Burn Memory Fit Geardo High Top Trainer",
"https://www.shoes.com//pm/skech/skech798492_42965_hd2.jpg"));
shoes.add(new Shoe("New Balance Fresh Foam Zante v3 Running Shoe",
"https://www.shoes.com/pi/newba/hd/newba805216_436896_hd.jpg"));
for(int i = 0 ; i <= 5 ; i++ ) {
shoes.addAll(shoes);
}
displayList();
}
private void displayList() {
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
RecyclerView.Adapter adapter = new ShoeAdapter(this, shoes);
shoeRecyclerView.setLayoutManager(layoutManager);
shoeRecyclerView.setAdapter(adapter);
}
private void setOnOffsetChangedListener() {
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
boolean isDisplayed = false;
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
int totalScroll = appBarLayout.getTotalScrollRange();
if (totalScroll + verticalOffset == 0) {
if (actionBar != null) {
actionBar.setTitle("Sneakers");
}
isDisplayed = true;
} else if (isDisplayed) {
if (actionBar != null)
actionBar.setTitle("");
isDisplayed = false;
}
}
});
}
}
**(d). MainActivity.java***
E finalmente a principal atividade
.
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import com.ariannejad.mostafa.transitional_imageview_implementation.R;
import com.mostafaaryan.transitionalimageview.TransitionalImageView;
import com.mostafaaryan.transitionalimageview.model.TransitionalImage;
import com.squareup.picasso.Picasso;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
private String imageUrl = "https://image.freepik.com/free-icon/android-logo_318-54237.jpg";
TransitionalImageView tiv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tiv = (TransitionalImageView) findViewById(R.id.sample_image);
loadImage();
}
private void loadImage() {
/*
ImageLoader imageLoader;
imageLoader = ImageLoader.getInstance();
imageLoader.init(ImageLoaderConfiguration.createDefault(this));
AsyncTask.execute(new Runnable() {
@Override
public void run() {
DisplayImageOptions dio = new DisplayImageOptions.Builder()
.cacheInMemory(false).build();
final Bitmap bmp = imageLoader.loadImageSync(imageUrl, dio);
runOnUiThread(new Runnable() {
@Override
public void run() {
tiv.setImage(bmp);
}
});
}
});*/
/* Glide.with(this).asBitmap().load(imageUrl).into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
tiv.setImage(resource);
}
}); */
AsyncTask.execute(new Runnable() {
@Override
public void run() {
try {
final Bitmap b = Picasso.with(MainActivity.this).load(imageUrl).get();
runOnUiThread(new Runnable() {
@Override
public void run() {
// tiv.setImage(b);
TransitionalImage transitionalImage = new TransitionalImage.Builder()
.duration(500)
.backgroundColor(ContextCompat.getColor(MainActivity.this, R.color.colorAccent))
//.image(R.drawable.sample_image)
.image(b)
.create();
tiv.setTransitionalImage(transitionalImage);
}
});
} catch (IOException e) {e.printStackTrace();}
}
});
}
public void onClickShoes(View view) {
startActivity(new Intent(this, ShoeListActivity.class));
}
}
Demo
Aqui está a demonstração do que você recebe quando executa o projeto.
Download
Aqui estão os links para download.
Kotlin 'Transição Compartilhada' RecyclerView e Fragmentos
Este é também um exemplo simples de transições compartilhadas escrito em Kotlin. Desta vez, porém, uma visão do reciclador é o elemento compartilhado entre dois "fragmentos".
**Ferramentas***
Aqui estão as coisas a ter em mente:
- Linguagem de Programação - Kotlin
- Mínimo SDK - 21
1. Criar Transições
Em uma pasta conhecida como transições sob recursos adicionar o seguinte:
**(a). change_bounds.xml***
<?xml version="1.0" encoding="utf-8"?>
<transitionSet>
<changeBounds />
</transitionSet>
**(b). change_image_transform.xml***
Então:
<?xml version="1.0" encoding="utf-8"?>
<transitionSet>
<changeImageTransform />
</transitionSet>
2. Layouts de design
Você encontrará os layouts no código.
3. Escreva o código
Neste caso, o código está escrito em Kotlin.
**(a). Fragmento1.kt***
Aqui está o código para o primeiro fragmento
.
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
import android.transition.ChangeBounds
import android.transition.ChangeImageTransform
class Fragment1: Fragment() {
private lateinit var lm: LinearLayoutManager
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.activity_main, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lm = LinearLayoutManager(context, LinearLayoutManager.VERTICAL,
false)
rv.layoutManager = lm
val adapter = MainActivity.Adapter()
rv.adapter = adapter
btn.setOnClickListener {
// val changeImageTransform =
// TransitionInflater.from(context).inflateTransition(R.transition.change_image_transform)
// val changeBoundsTransform =
// TransitionInflater.from(context).inflateTransition(R.transition.change_bounds)
sharedElementReturnTransition = ChangeBounds()
sharedElementEnterTransition = ChangeImageTransform()
exitTransition = ChangeBounds()
val fragment2 = Fragment2()
// Setup transition on second fragment
fragment2.sharedElementEnterTransition = ChangeBounds()
fragment2.enterTransition = ChangeBounds();
val firstVisiblePosition = lm.findFirstVisibleItemPosition()
val lastVisiblePosition = lm.findLastVisibleItemPosition()
val transaction = fragmentManager!!.beginTransaction()
.replace(R.id.container, fragment2, fragment2::class.java.simpleName)
.addToBackStack("name")
for (i in firstVisiblePosition..lastVisiblePosition) {
val holderForAdapterPosition =
rv.findViewHolderForAdapterPosition(i) as MainActivity.Adapter.Holder
val itemView = holderForAdapterPosition.itemView
transaction.addSharedElement(itemView, "unique_key_$i")
}
transaction.commit()
}
}
}
**(b). Fragmento2.kt***
Adicione o seguinte código no segundo fragmento
.
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
class Fragment2: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.activity_main, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val lm = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL,
false)
rv.layoutManager = lm
val adapter = MainActivity.Adapter()
rv.adapter = adapter
btn.setOnClickListener {
}
postponeEnterTransition()
}
override fun onStart() {
super.onStart()
rv.post {
startPostponedEnterTransition()
}
}
}
**(c). ScndActivity.kt***
Depois a segunda atividade
.
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
class ScndActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val lm = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,
false)
rv.layoutManager = lm
val adapter = MainActivity.Adapter()
rv.adapter = adapter
btn.setOnClickListener {
// val currentOrientation = lm.orientation
// if (currentOrientation == LinearLayoutManager.VERTICAL) {
// lm.orientation = LinearLayoutManager.HORIZONTAL
// } else {
// lm.orientation = LinearLayoutManager.VERTICAL
// }
// adapter.notifyItemRangeChanged(1, adapter?.itemCount ?: 0)
}
supportPostponeEnterTransition()
rv.post {
supportStartPostponedEnterTransition()
}
}
}
**(d). MainActivity.kt***
E por último, a principal atividade
,
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class MainActivity : AppCompatActivity() {
private lateinit var lm: LinearLayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.cont)
val fragment1 = Fragment1()
supportFragmentManager.beginTransaction()
.add(R.id.container, fragment1, Fragment1::class.java.simpleName)
.commit()
// lm = LinearLayoutManager(this, LinearLayoutManager.VERTICAL,
// false)
// rv.layoutManager = lm
// val adapter = Adapter()
// rv.adapter = adapter
// btn.setOnClickListener {
// val firstVisiblePosition = lm.findFirstVisibleItemPosition()
// val lastVisiblePosition = lm.findLastVisibleItemPosition()
// val pairs = ArrayList<Pair<View, String>>()
// for (i in firstVisiblePosition..lastVisiblePosition) {
// val holderForAdapterPosition =
// rv.findViewHolderForAdapterPosition(i) as Adapter.Holder
// val itemView = holderForAdapterPosition.itemView
// pairs.add(Pair(itemView, "unique_key_$i"))
// }
// val bundle = ActivityOptions.makeSceneTransitionAnimation(
// this,
// *pairs.toTypedArray()
// ).toBundle()
// val fragment1 = Fragment1()
// supportFragmentManager.beginTransaction()
// .add(fragment1, Fragment1::class.java.simpleName)
// .commit()
// startActivity(Intent(this, ScndActivity::class.java), bundle)
// }
}
override fun onResume() {
super.onResume()
}
class Adapter : RecyclerView.Adapter<Adapter.Holder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder =
Holder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_item,
parent,
false
)
)
override fun getItemCount(): Int = 10
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.bind(position)
}
class Holder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(position: Int) {
itemView.transitionName = "unique_key_$position"
}
}
}
}
Demo
Aqui está o que você recebe quando executa o projeto.
Download
Java 'Transição Compartilhada' com Fragmentos e Botão de Ação Flutuante
Este é um exemplo simples de uma classe para utilizar uma transição de elementos compartilhados dentro de "fragmentos" em uma "atividade andróide". A linguagem de programação é Java. Embora não seja escrita em androidx, você pode facilmente atualizá-la para androidx `fragments' e ela não utiliza nenhuma biblioteca de terceiros partt.
Transições
Estes estão escritos em XML. Tyically você cria um diretório de recursos de transição e coloca o XML.
**(a). shared_enter_transition.xml***
Aqui está o código:
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/default_anim_duration">
<changeTransform/>
<arcMotion
android:minimumHorizontalAngle="0"
android:minimumVerticalAngle="15"
android:maximumAngle="90" />
<changeBounds />
</transitionSet>
Atividades
Aqui estão as "atividades".
**(a). MainActivity.java***
Aqui está a principal atividade
:
import android.animation.Animator;
import android.app.Fragment;
import android.os.Bundle;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.ActionBarActivity;
import android.transition.Fade;
import android.transition.Transition;
import android.transition.TransitionInflater;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
public class FabActivity extends ActionBarActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fab);
getFragmentManager()
.beginTransaction()
.add(R.id.frag_content, TitleFragment.newInstance())
.commit();
}
public static class TitleFragment extends Fragment {
public static TitleFragment newInstance() {
return new TitleFragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_fab_title, container, false);
final View fabbutton = view.findViewById(R.id.fab);
fabbutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final ControlsFragment controlsFragment = ControlsFragment.newInstance();
setupSharedElementTransition(controlsFragment);
Fade f = new Fade();
f.setStartDelay(250);
setExitTransition(f);
getFragmentManager()
.beginTransaction()
.replace(R.id.frag_content, controlsFragment)
.addToBackStack("controls")
.addSharedElement(fabbutton, "pause_button")
.commit();
}
});
return view;
}
private void setupSharedElementTransition(final ControlsFragment controlsFragment) {
Transition sharedTransition = TransitionInflater.from(getActivity()).inflateTransition(R.transition.shared_enter_transition);
controlsFragment.setSharedElementEnterTransition(sharedTransition);
controlsFragment.setSharedElementReturnTransition(sharedTransition);
sharedTransition.addListener(new Transition.TransitionListener() {
@Override
public void onTransitionEnd(Transition transition) {
controlsFragment.revealContent();
}
@Override
public void onTransitionStart(Transition transition) {
}
@Override
public void onTransitionCancel(Transition transition) {
}
@Override
public void onTransitionPause(Transition transition) {
}
@Override
public void onTransitionResume(Transition transition) {
}
});
}
}
public static class ControlsFragment extends Fragment {
public static ControlsFragment newInstance() {
return new ControlsFragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_fab_controls, container, false);
}
public void revealContent() {
View layout = getView().findViewById(R.id.controls_layout);
animateRevealColor(layout);
}
private void animateRevealColor(View targetView) {
int cx = (targetView.getLeft() + targetView.getRight()) / 2;
int cy = (targetView.getTop() + targetView.getBottom()) / 2;
cx += targetView.getTranslationX();
cy += targetView.getTranslationY();
int finalRadius = Math.max(targetView.getWidth(), targetView.getHeight());
Animator anim = ViewAnimationUtils.createCircularReveal(targetView, cx, cy, 0, finalRadius);
targetView.setBackgroundColor(getResources().getColor(R.color.accent_material_light));
anim.setDuration(getResources().getInteger(R.integer.default_anim_duration));
anim.setInterpolator(new AccelerateInterpolator());
anim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationEnd(Animator animator) {
animateScaleButton(getView().findViewById(R.id.ff_button));
animateScaleButton(getView().findViewById(R.id.rew_button));
}
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
anim.start();
}
private void animateScaleButton(View view) {
ViewCompat.animate(view)
.scaleX(1)
.scaleY(1)
.setDuration(250)
.start();
}
}
}
Demo
Aqui está a demonstração do que você recebe quando executa o projeto.
Links para download
- Faça o download direto do código aqui. (Por favor, atualize-o para androidx e compartilhe novamente seu código)
- Siga o Autor aqui.