I'm trying to make the following effect:
I have a custom ScrollView (in oder to get the onScrollChanged listener) and a custom View inside it. In the custom View I succeed to place the item as I want.
Here my customView:
public class CustomView extends FrameLayout {
private TextView nameView;
private TextView emailView;
private ImageView addressView;
private Tracks track ;
private double scrollProgress = 0.0;
private double topViewScaleFactor = 2.0;
private double collapsedViewHeight = 200.0;
private double expandedViewHeight = 700.0;
private double scrollProgressPerView = expandedViewHeight;
View v;
View firstItem;
View secondView;
int itemMinHeight = 200;
int itemMaxHeight = 700;
public CustomView(MyScrollView paramEventListView, Context paramContext){
super(paramContext);
}
public CustomView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public CustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
int m = getMeasuredWidth();
int itemWidth = (r-l)/getChildCount();
// int itemHeight = (b-t)/getChildCount();
firstItem = getChildAt(0);
//firstItem.layout(0, 0, r-l, itemMaxHeight);
firstItem.measure(View.MeasureSpec.makeMeasureSpec(m, MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(itemMaxHeight, MeasureSpec.EXACTLY));
firstItem.layout(0, 0, r-l, itemMaxHeight);
secondView = getChildAt(1);
secondView.measure(View.MeasureSpec.makeMeasureSpec(m, MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(itemMinHeight, MeasureSpec.EXACTLY));
secondView.layout(0, itemMaxHeight, r-l, itemMinHeight+itemMaxHeight);
int FirstAndSEcondItemHeight = firstItem.getHeight() + secondView.getHeight();
for(int i=2; i< this.getChildCount(); i++){
v = getChildAt(i);
// v.layout(itemWidth*i, 0, (i+1)*itemWidth, b-t);
v.layout(0, FirstAndSEcondItemHeight + (itemMinHeight*(i-2)), r-l, FirstAndSEcondItemHeight + ((i-1)*itemMinHeight));
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int heightMeasured = 0;
/*for each child get height and
heightMeasured += childHeight;*/
for(int i=0; i< this.getChildCount(); i++){
heightMeasured += getChildAt(i).getHeight();
}
//If I am in a scrollview i got heightmeasurespec == 0, so
if(heightMeasureSpec == 0){
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightMeasured, MeasureSpec.EXACTLY);
}
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(this.getSuggestedMinimumHeight(), heightMeasureSpec));
}
Here my Custom ScrollView:
public class MyScrollView extends ScrollView{
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
add(context);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScrollViewListener(ScrollViewListener scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
// How can I acces to each child in the customView class, and change their height depending on the scrollChanged
}
But now I need to change the item height when I scroll the scrollview. I don't know what to put in the onScrollChanged...
If someone has an idea?
Thanks
Perhaps you want to force the view to redraw by calling invalidate()?
From comment: I think you need to move your code in the onMeasure method to replace the "TODO" stub, so that super.onMeasure() is called after your manipulations. And make sure you're passing the new width/height calculations to it.
Related
The custom View in my custom ViewGroup refuses to show the drawable given to it by calling setImageResource(). It is laid out as I need it, however, as you can see in this screenshot, it's empty:
Also, it won't react on an onClick event.
Here's the java code for my Activity
public class MainActivity extends Activity {
public static final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BattleShipsGameBoard gb = (BattleShipsGameBoard) findViewById(R.id.gameboard);
Tile tile = new Tile(this);
tile.setImageResource(R.drawable.tile_hit);
tile.setGameObjectType(BattleShipsGameBoard.LayoutParams.LAYOUT_TYPE_TILE);
tile.setPosition(new Point(50, 50));
tile.setWidth(90);
tile.setHeight(90);
gb.addView(tile);
}
}
and my custom view
public class Tile extends ImageView {
#SuppressWarnings("unused")
private static final String TAG = "Tile";
public int tag;
public int gameObjectType;
public Point position = new Point(0, 0);
public int mWidth = 1;
public int mHeight = 1;
public boolean isSelected = false;
public Tile(Context context) {
super(context);
setLayoutParams(new BattleShipsGameBoard.LayoutParams(
BattleShipsGameBoard.LayoutParams.WRAP_CONTENT,
BattleShipsGameBoard.LayoutParams.WRAP_CONTENT));
}
public Tile(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Tile(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void confirmChangesInLayout() {
BattleShipsGameBoard.LayoutParams lp = (BattleShipsGameBoard.LayoutParams) this
.getLayoutParams();
lp.setPosition(this.position);
lp.setWidth(this.mWidth);
lp.setHeight(this.mHeight);
setLayoutParams(lp);
invalidate();
requestLayout();
}
//... getters and setters, the setters all call confirmChangesInLayout()
}
my simple custom ViewGroup:
public class BattleShipsGameBoard extends ViewGroup {
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public Point position = new Point(0, 0);
public int type = 0;
public int height = 0;
public int width = 0;
//getters and setters
}
public BattleShipsGameBoard(Context context) {
super(context);
}
public BattleShipsGameBoard(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BattleShipsGameBoard(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private float unitWidth;
private float unitHeight;
private int parentWidth;
private int parentHeight;
/**
* count of units the screen estate is divided by
*/
public static int unitCount = 100;
/**
* Rectangle in which the size of a child is temporarily stored
*/
private Rect mTmpChildRect = new Rect();
/**
* lays out children
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Log.d(TAG, "-------------STARTING LAYOUT, " + getChildCount() + " children -------------");
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
Point pos = lp.getPosition();
int height = lp.getHeight();
int width = lp.getWidth();
measureChild(child, parentWidth, parentHeight);
mTmpChildRect.left = (int) ((pos.x - (width / 2)) * unitWidth);
mTmpChildRect.right = (int) ((pos.x + (width / 2)) * unitWidth);
mTmpChildRect.top = (int) ((pos.y + (height / 2)) * unitHeight);
mTmpChildRect.bottom = (int) ((pos.y - (height / 2)) * unitHeight);
child.layout(mTmpChildRect.left, mTmpChildRect.top, mTmpChildRect.right, mTmpChildRect.bottom);
Log.d(TAG,
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
parentHeight = MeasureSpec.getSize(heightMeasureSpec);
parentWidth = MeasureSpec.getSize(widthMeasureSpec);
unitHeight = parentHeight / unitCount;
unitWidth = parentWidth / unitCount;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
child.measure(widthMeasureSpec, heightMeasureSpec);
}
}
setMeasuredDimension(parentWidth, parentHeight);
}
/**
* Any layout manager that doesn't scroll will want this.
*/
#Override
public boolean shouldDelayChildPressedState() {
return false;
}
}
I just found the problem.
In the onLayout() method I mixed up mTmpChildRect.top and mTmpChildRect.bottom which is why it looked like it was laid out correctly but nothing could be drawn.
I am facing problem while reading Animation scale duration flag in Marshmallow. Same code is working in earlier version of Marshmallow (6.0.0).
I am using android.app.Fragment.setCustomTransition() method for fragment animation. When Animation scale duration is 0 in developer option settings, fragments do not get displayed. So i had to disable animation over this condition.
My code snippet:
public static boolean isAnimationOff()
{
final float animatorSpeed;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
{
animatorSpeed = Settings.Global.getFloat(
context.getContentResolver(),
Settings.Global.ANIMATOR_DURATION_SCALE,
0);
}
else
{
animatorSpeed = Settings.System.getFloat(
context.getContentResolver(),
Settings.System.ANIMATOR_DURATION_SCALE,
0);
}
return animatorSpeed == 0;
}
The problem is :this code is returning true on each invoke even if animation scale duration is not 0.
Has anyone faced this issue?
Edit1
Following is the code snippet where I use isAnimationOff() method to load a fragment:
private void loadFragment(Fragment fragment)
{
FragmentTransaction transaction = getFragmentManager().beginTransaction();
if (!Constants.isAnimationOff())
transaction.setCustomAnimations(animSlideIn, animSlideOut);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE).replace(R.id.frameTopContainer, fragment)
.commitAllowingStateLoss();
}
Edit2
Here is the custom LinearLayout that has custom property:
public class FractionLinearLayout extends LinearLayout
{
DisplayMetrics matrics = getContext().getResources().getDisplayMetrics();
public FractionLinearLayout(Context context, AttributeSet attrs,
int defStyleAttr)
{
super(context, attrs, defStyleAttr);
// TODO Auto-generated constructor stub
}
public FractionLinearLayout(Context context)
{
super(context);
// TODO Auto-generated constructor stub
}
public FractionLinearLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
// TODO Auto-generated constructor stub
}
public float getFractionTranslationX()
{
return getWidth() > 0 ? super.getTranslationX() / getWidth() : Float.MAX_VALUE;
}
public void setFractionTranslationX(float translationX)
{
int width = getWidth();
super.setTranslationX(width > 0 ? width * translationX : Float.MAX_VALUE);
}
public float getFractionTranslationY()
{
return getHeight() > 0 ? super.getTranslationX() / getHeight() : Float.MAX_VALUE;
}
public void setFractionTranslationY(float translationY)
{
int height = getHeight();
super.setTranslationY(height > 0 ? height * translationY : Float.MAX_VALUE);
}
public float getAnimWidth()
{
return getLayoutParams().width;
}
public void setAnimWidth(int animWidth)
{
getLayoutParams().width = animWidth;
requestLayout();
}
}
I had an issue in my FractionLinearLayout class. In set setFractionTranslationX() method, getWidth() was returning 0 at the very first call. That led my code to prevent Fragment to appear when animation is off. Now i have replaced getWidth() line with getResources().getDisplayMetrics().widthPixels and everything is working fine.
Updated Code:
public class FractionLinearLayout extends LinearLayout
{
DisplayMetrics matrics = getContext().getResources().getDisplayMetrics();
public FractionLinearLayout(Context context, AttributeSet attrs,
int defStyleAttr)
{
super(context, attrs, defStyleAttr);
// TODO Auto-generated constructor stub
}
public FractionLinearLayout(Context context)
{
super(context);
// TODO Auto-generated constructor stub
}
public FractionLinearLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
// TODO Auto-generated constructor stub
}
public float getFractionTranslationX()
{
return getWidth() > 0 ? super.getTranslationX() / getWidth() : 0;
}
public void setFractionTranslationX(float translationX)
{
int width = getResources().getDisplayMetrics().widthPixels;
super.setTranslationX(width > 0 ? width * translationX : 0);
}
public float getFractionTranslationY()
{
return getHeight() > 0 ? super.getTranslationY() / getHeight() : 0;
}
public void setFractionTranslationY(float translationY)
{
int height = getResources().getDisplayMetrics().heightPixels;
super.setTranslationY(height > 0 ? height * translationY : 0);
}
public float getAnimWidth()
{
return getLayoutParams().width;
}
public void setAnimWidth(int animWidth)
{
getLayoutParams().width = animWidth;
requestLayout();
}
}
But Still I don't knowl, why is ANIMATOR_DURATION_SCALE flag not working in Marshmallow.
How can I determine the direction of a vertical ScrollView?
I need to hide and display another linear layout accoding to scrollview scroll
You can use the onScrollChanged(int l, int t, int oldl, int oldt) method for that. As there isn't a setter for a listener, but just that method, you will have to create your own ScrollView class and override that method and do your thing in your implementation.
public class ObservableScrollView extends ScrollView {
private static final int DEFAULT_THRESHOLD_DP = 4;
private ScrollDirectionListener scrollDirectionListener;
private int scrollThreshold;
public ObservableScrollView(Context context) {
this(context, null);
}
public ObservableScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ObservableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
scrollThreshold = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_THRESHOLD_DP, context.getResources().getDisplayMetrics());
}
#Override
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
super.onScrollChanged(x, y, oldX, oldY);
if (Math.abs(y - oldY) > scrollThreshold && scrollDirectionListener != null) {
if (y > oldY) {
scrollDirectionListener.onScrollUp(Math.abs(y - oldY));
} else {
scrollDirectionListener.onScrollDown(Math.abs(y - oldY));
}
}
}
public void setScrollThresholdInPx(int px) {
scrollThreshold = px;
}
public void setScrollThresholdInDp(Context context, float dp) {
scrollThreshold = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
}
public void setOnScrollDirectionListener(ScrollDirectionListener listener) {
scrollDirectionListener = listener;
}
public interface ScrollDirectionListener {
void onScrollDown(int pixels);
void onScrollUp(int pixels);
}}
got answer :) use this custom scrollview
ScrollView.setOnScrollDirectionListener(new ObservableScrollView.ScrollDirectionListener() {
#Override
public void onScrollDown(int pixels) {
contenttool.setVisibility(View.INVISIBLE);
}
#Override
public void onScrollUp(int pixels) {
contenttool.setVisibility(View.VISIBLE);
}
});
To improve TextView rendering in Instagram, the engineers in Instagram provide a hack in here,they use a custom view(TextLayoutView) to cache the text.Layout, but in this post, they don't give us a demo or tell us how to use it, so if I want to use this hack, how could I do?
This is my simple implementation:
TextLayoutView:
public class TextLayoutView extends View {
private Layout mLayout;
public TextLayoutView(Context context) {
this(context, null);
}
public TextLayoutView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TextLayoutView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setFocusable(true);
}
#Override
protected void onDraw(Canvas canvas) {
long t1=System.currentTimeMillis();
super.onDraw(canvas);
canvas.save();
if (mLayout != null) {
canvas.translate(getPaddingLeft(), getPaddingTop());
mLayout.draw(canvas);
}
canvas.restore();
long t2=System.currentTimeMillis();
Log.i("TEST", "onDraw::"+(t2-t1));
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
long t1=System.currentTimeMillis();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mLayout != null) {
setMeasuredDimension(
getPaddingLeft() + getPaddingRight() + mLayout.getWidth(),
getPaddingTop() + getPaddingBottom() + mLayout.getHeight());
}
long t2=System.currentTimeMillis();
Log.i("TEST", "onMeasure::"+(t2-t1));
}
public void setTextLayout(Layout layout) {
mLayout = layout;
requestLayout();
}}
you can use it just this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
......
viewHolder.textView.setTextLayout(getLayout(mList.get(position)));
return convertView;
}
private final Map<String, Layout> mLayoutMap = new ConcurrentHashMap<String, Layout>();
private Layout getLayout(String str) {
Layout layout = mLayoutMap.get(str);
if (layout == null) {
TextPaint textPaint = new TextPaint();
textPaint.setTextSize(20);
layout = new StaticLayout(str, textPaint, width, Alignment.ALIGN_CENTER,
1.0f, 0.0f, true);
mLayoutMap.put(str, layout);
}
return layout;
}
I've got a scrollView with dinamically loading content. Sometimes there can be a lot of content, so I would like to load more when user scrolls to bottom.
I searced for suitable metods and found two:
onScrollChanged()
and
getScrollY()
But I dont know how to use it for my purposes.
Please give me some advice.
Scroll view does not provide any method to check if you've reached bottom of the view so the best technique is to extend your own custom view with scroll view . This is how i implemented in my app.
Step 1:Create a custom class for scroll view
public class ObservableScrollView extends ScrollView {
private ScrollViewListener scrollViewListener = null;
public ObservableScrollView(Context context) {
super(context);
}
public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScrollViewListener(ScrollViewListener scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
View view = (View) getChildAt(getChildCount() - 1);
int diff = (view.getBottom() - (getHeight() + getScrollY()));
if (diff == 0) { // if diff is zero, then the bottom has been reached
if (scrollViewListener != null) {
scrollViewListener.onScrollEnded(this, x, y, oldx, oldy);
}
}
super.onScrollChanged(x, y, oldx, oldy);
}
}
Step 2:Create a scroll view Listener Interface
public interface ScrollViewListener {
void onScrollEnded(ObservableScrollView scrollView, int x, int y, int oldx, int oldy);
}
Step 3:Add the view in your layout
<com.platinumapps.facedroid.ObservableScrollView
android:id="#+id/scrollView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</com.platinumapps.facedroid.ObservableScrollView>
Step 4:Implement that interface in your class
public class MyFragment extends Fragment implements ScrollViewListener
#Override
public void onScrollEnded(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
//Perform your action
}
And you are done
I think the better way would be to use ListView.But if you only required ScrollView.
First you fix a threshold height of your ScrollView so whenever you crosses that limit load new data & append to ScrollView & reset your threshold height.
This is how you can try..
Create a customized ScrollView like this.
public class ObservableScrollView extends ScrollView
{
private ScrollViewListener scrollViewListener = null;
public ObservableScrollView(Context context)
{
super(context);
}
public ObservableScrollView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
public ObservableScrollView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public void setScrollViewListener(ScrollViewListener scrollViewListener)
{
this.scrollViewListener = scrollViewListener;
}
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy)
{
super.onScrollChanged(x, y, oldx, oldy);
if (scrollViewListener != null)
{
scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
}
}
}
& then create an interface
public interface ScrollViewListener
{
void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy);
}
& your XML layout should be like this
<LinearLayout 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"
android:orientation="vertical" >
<com.solve.stackoverflow.ObservableScrollView
android:id="#+id/endless_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:id="#+id/endless_scrollview_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</com.solve.stackoverflow.ObservableScrollView>
</LinearLayout>
& finally use all these in your Activity
public class EndLessActivity extends Activity implements ScrollViewListener
{
ObservableScrollView scrollView;
LinearLayout layout;
int threshold = 20; //Setting threshold
#Override
protected void onCreate(Bundle savedInstanceState)
{
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.endless_scrollview);
scrollView = (ObservableScrollView) findViewById(R.id.endless_scrollview);
layout = (LinearLayout) findViewById(R.id.endless_scrollview_layout);
scrollView.setScrollViewListener(this);
appendData();
}
#Override
public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy)
{
// TODO Auto-generated method stub
if (y > threshold)
appendData();
}
void appendData()
{
for (int i = 0; i < 15; i++)
{
threshold += 10;//Reseting threshold.
TextView tv = new TextView(this);
tv.setBackgroundResource(R.drawable.ic_launcher);
layout.addView(tv);
}
}
}
Try this & let me know, whether it solve your problem or not.
Thanks