I like the animation that occurs when scrolling through posts in the Google+ app, but I can't work out how they achieve it.
What techniques are employed to animate posts as they appear? I'm not looking for the animation itself, just how I'd apply any animation to a list of scrollable items.
Thanks.
After some testing I think I got something similar to work;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final LinearLayout list = new LinearLayout(this);
list.setOrientation(LinearLayout.VERTICAL);
ScrollView scrollView = new ScrollView(this) {
Rect mRect = new Rect();
#Override
public void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
for (int i = 0; i < list.getChildCount(); ++i) {
View v = list.getChildAt(i);
// Tag initially visible Views as 'true'.
mRect.set(l, t, r, b);
v.setTag(getChildVisibleRect(v, mRect, null));
}
}
#Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
for (int i = 0; i < list.getChildCount(); ++i) {
View v = list.getChildAt(i);
mRect.set(getLeft(), getTop(), getRight(), getBottom());
// If tag == 'false' and View is visible we know that
// View became visible during this scroll event.
if ((Boolean) v.getTag() == false
&& getChildVisibleRect(v, mRect, null)) {
AlphaAnimation anim = new AlphaAnimation(0, 1);
anim.setDuration(1000);
v.startAnimation(anim);
v.setTag(true);
}
}
}
};
scrollView.addView(list);
for (int i = 0; i < 20; ++i) {
TextView tv = new TextView(this);
tv.setText("Test");
tv.setTextSize(72);
tv.setTextColor(Color.WHITE);
tv.setBackgroundColor(Color.GRAY);
list.addView(tv);
}
setContentView(scrollView);
}
Scrolling down the list should trigger alpha animation once new TextViews become visible.
There's a library for that, it seems to do the job well:
https://github.com/cuub/sugared-list-animations
Related
I want to create custom container, that can lay children one by one from bottom with offset. Currently I created such container but I have problems with animation, when view is added to container it should slide from bottom, when view is remove it should slide to bottom. With add animation all fine, but with remove I got some issues, views dont want to go in needed position?
This is onLayout() method:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int offset;
if (i > (itemCount - VISIBLE_ITEMS)) {
offset = (itemCount - i - 1) * this.offset;
}
// we adding invisible items
else {
offset = (VISIBLE_ITEMS - 1) * this.offset;
}
int bottom = getBottom() - offset;
int left = getLeft();
int right = getRight();
int top = bottom - child.getMeasuredHeight();
child.layout(left, top, right, bottom);
}
}
This is method for adding new view:
public void animateAdd(final View view){
addView(view);
final ViewTreeObserver observer = getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
AnimatorSet animator = new AnimatorSet();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int startY = i == getChildCount() - 1 ? child.getHeight() : offset;
if (isNeedToAnimate(i)) {
Log.d(TAG, "onPreDraw: startY = " + startY);
animator.playTogether(ObjectAnimator.ofFloat(child, TRANSLATION_Y, startY, 0));
animator.playTogether(createColorAnimator(child, i));
}
}
animator.setDuration(300);
animator.start();
return true;
}
});
}
This is method for remove action:
public void animateRemove() {
if (getChildCount() == 0) {
return;
}
final View removeView = getChildAt(getChildCount() - 1);
removeViewInLayout(removeView);
final ViewTreeObserver observer = getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
final AnimatorSet animator = new AnimatorSet();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int endY = i == getChildCount() - 1 ? child.getHeight() : offset;
if (isNeedToAnimateRemove(i)) {
Animator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, endY);
animator.playTogether(anim);
animator.playTogether(createColorAnimator(child, i));
}
}
animator.setDuration(300);
animator.start();
return true;
}
});
}
public class CustomView extends SurfaceView {
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int itemWidth = (r - l) / getChildCount();
for (int i = 0; i < this.getChildCount(); i++) {
View v = getChildAt(i);
v.layout(itemWidth * i, 0, (i + 1) * itemWidth, b - t);
}
}
}
In the above overriden method getChildCount() and getChildAt(i) throws no such method found.
It is correct. SurfaceView extends View not ViewGroup. getChildCount() and getChildAt(i) are methods of ViewGroup
I have created a GridView control, which inhertis from a ScrollView, the idea of this control, is that it will contain multiple Views arranged in a grid format with a given number of columns and rows.
When the view is first built, the GridView doesn't know the size of its container, so I wait until the onSizeChanged method is called, then I apply the relevant sizing.
When the below is run, it doesn't re-size the grid to show it correctly, each control is only as big as it needs to be to show the text.
When the `onSizeChanged' method is called, it has the correct size, and applies the correct size to each child view, but it doesn't affect the way the controls are drawn (i.e. they're still all bunched up on the top left of the screen).
Despite this, I have actually got it working, but it draws twice. I do this by creating a Runnable which calls ResizeList. Then calling new Handler().post(r) straight after I call BuildIt.
Although this is a solution, I just don't understand why it doesn't work in the below form.
Incidentally, if the GridView is the main View added to the Activity, it displays fine, it's only when it's subsequently added. Which is why I have the Button, which you have to press to show the grid.
Can anyone suggest why the below code doesn't work properly?
public class MainActivity extends Activity {
GridView sv;
FrameLayout flay;
#Override protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
flay=new FrameLayout(this);
this.setContentView(flay);
Button b=new Button(this);
b.setText("press me to show grid view");
b.setOnClickListener(ocl);
flay.addView(b);
}
OnClickListener ocl=new OnClickListener()
{
#Override public void onClick(View v)
{
BuildIt();
}};
private void BuildIt()
{
flay.removeAllViews(); // remove the button control
sv=new GridView(this);
for (int c1=0 ; c1<30 ; c1++)
{
TextView tv=new TextView(this);
tv.setText("Item "+c1);
tv.setGravity(android.view.Gravity.CENTER);
sv.addListItem(tv);
}
flay.addView(sv);
sv.ConstructList();
}
}
The GridView class
public class GridView extends ScrollView
{
final int rows=4;
final int cols=4;
private ArrayList<View> allViews=new ArrayList<View>();
private LinearLayout ll;
public GridView(Context context)
{
super(context);
ll=new LinearLayout(context);
ll.setOrientation(LinearLayout.VERTICAL);
this.addView(ll);
}
public void addListItem(View v)
{
allViews.add(v);
}
public void ConstructList()
{
int c1=0;
ll.removeAllViews(); // Just in case we're re-building
LinearLayout row=null;
for (View v : allViews)
{
if (c1%cols==0)
{
row=new LinearLayout(this.getContext());
ll.addView(row);
}
row.addView(v);
c1++;
}
}
private void ResizeList()
{
int useHeight=getHeight()/rows;
int useWidth=getWidth()/cols;
LinearLayout.LayoutParams lpCol=new LinearLayout.LayoutParams(useWidth, useHeight);
Log.i("log","About to set width/height="+useWidth+"/"+useHeight);
int numKids= ll.getChildCount();
for (int c1=0 ; c1<numKids ; c1++)
{
LinearLayout ll2=(LinearLayout)ll.getChildAt(c1);
for (int c2=0 ; c2<ll2.getChildCount() ; c2++) // use getChildCount rather than cols, just in case it's the last one
{
View v=ll2.getChildAt(c2);
v.setLayoutParams(lpCol);
}
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
ResizeList();
}
}
I have a function which is used to resize the child's width and height in gridView.
May be this could help you :
public static void setGridChild_Height(GridView gridView, int columns) {
ListAdapter listAdapter = gridView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
int items = listAdapter.getCount();
int rows = 0;
for (int j = 0; j < items; j++) {
View view = gridView.getChildAt(j);
if (view != null && view.getHeight() > totalHeight) {
totalHeight = view.getHeight();
}
}
System.out.println("totalHeight -> " + totalHeight);
if (totalHeight > 0) {
for (int j = 0; j < items; j++) {
View view = gridView.getChildAt(j);
if (view != null && view.getHeight() < totalHeight) {
view.setMinimumHeight(totalHeight);
}
}
}
// View listItem = listAdapter.getView(0, null, gridView);
// listItem.measure(0, 0);
// totalHeight = listItem.getMeasuredHeight();
//
// float x = 1;
// if (items > columns) {
// x = items / columns;
// rows = (int) (x + 1);
// totalHeight *= rows;
// }
//
// ViewGroup.LayoutParams params = gridView.getLayoutParams();
// params.height = totalHeight;
// gridView.setLayoutParams(params);
}
Try to change logic as per your requirement.
Code is not tested perfectly.
It's because onSizeChanged when newly added to the view hierarchy uses it's old sizes of "0" (according to the docs: http://developer.android.com/reference/android/view/View.html#onSizeChanged(int, int, int, int))
I think what you want is to a addOnLayoutChangedListener : http://developer.android.com/reference/android/view/View.html#addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener)
Using the ViewTreeObserver might be another option that will work for you: How can you tell when a layout has been drawn?
I am trying to create a transperancy mask.
I have created a slidingScreen which extends ViewGroup.
in the constructor i add:
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
LinearLayout roadMap = new LinearLayout(context);
roadMap.setLayoutParams(params);
this.addView(roadMap);
I then override the onDispatchDraw() method:
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
Log.d("touchy touch", "dispatch draw called");
canvas.drawColor(getResources().getColor(R.color.LIGHT_BLUE));
/* recreate the blue rectangle on the canvas */
path = new Path();
path.moveTo(0.0f, 0.0f); // Top Left
path.lineTo(0.0f, 800.0f); // Bottom Left
path.lineTo(0.0f, 800.0f ); // Bottom Right
path.lineTo(this.x, 0.0f); // Top Right
path.close();
canvas.drawPath(path,paint);
}
The result is I have a transparent mask which is equal to the path created. This is not the problem. Unfortunatley roadMap is never displayed.
I tried to override:
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = this.getChildCount();
Log.d("children please", "count: "+count);
for (int i = 0; i < count; i++) {
View child = this.getChildAt(i);
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
child.invalidate();
child.bringToFront();
}
}
Yet still the linearlayout i am trying to attach does not appear.
Changed to Extend FrameLayout.
Removed this line:
canvas.drawColor(getResources().getColor(R.color.LIGHT_BLUE));
and then i was seeing the roadMap view!
What is the proper way to override onLayout method in a custom layout extending the RelativeLayout?
I'm trying to place all views in sort of a table. The idea is to fill one row with ImageViews until it's full and then continue in the new row.
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
int idOfViewToTheLeft = 1;
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(getContext(),);
ImageView bookmark;
for(int counter = 1; counter < getChildCount(); counter++) {
bookmark = (ImageView) findViewById(counter);
if(counter > 1) {
if(this.getWidth() > (bookmark.getLeft() + bookmark.getWidth())) {
params.addRule(RelativeLayout.RIGHT_OF, bookmark.getId() - 1);
} else {
params.addRule(RelativeLayout.BELOW, idOfViewToTheLeft);
idOfViewToTheLeft = bookmark.getId();
}
}
bookmark.setScaleType(ScaleType.CENTER_CROP);
}
}
}
I explain how to write custom layouts (and in particular a FlowLayout, which is what you want to do it seems like) in this presentation
Video available here.