onScrollChange() is not being called for every pixel scrolled on ScrollView - android

I want to add new child views after every 50 pixels of scrolling but the statement Log.d("scroll",scrollY+":"+oldScrollY); is missing some values i.e. scrollY = 149 and scrollY = 151 are there in Log but scrollY = 150 is missing. So the code doesn't run for scrollY = 150 and new child views is not added.
Although it works as it should for most of the values of scrollY but for few values of scrollY, the event is not being triggered.
How can i resolve this issue? Or is there any other way to achieve this.
public class MainActivity extends AppCompatActivity implements ScrollViewListener {
RelativeLayout parent;
TextView height;
ObservableScrollView scrollView;
int position=1;
View view1,view2;
#RequiresApi(api = Build.VERSION_CODES.M)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
parent = (RelativeLayout) findViewById(R.id.parent);
scrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
LayoutInflater inflater = LayoutInflater.from(this);
view1 = inflater.inflate(R.layout.education,null, false);
view1.setId(R.id.parent+position);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.LEFT_OF,R.id.divider);
params.topMargin = 100;
params.rightMargin = 3;
parent.addView(view1, params);
position++;
final Animation leftAnimation = new TranslateAnimation(view1.getX()-50,view1.getX(),view1.getY()+100,view1.getY());
leftAnimation.setDuration(1000);
view1.setAnimation(leftAnimation);
scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View view, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
Log.d("scroll",scrollY+":"+oldScrollY);
if((scrollY-oldScrollY)>0 && scrollY%50 == 0)
{
Log.d("scroll","abcghfg");
if(position%2==0)
{
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.RIGHT_OF,R.id.divider);
// params.topMargin = (position-1)*(view1.getLayoutParams().height)+position*100;
params.topMargin = 30;
params.leftMargin = 3;
params.addRule(RelativeLayout.BELOW,parent.getChildAt(position-1).getId());
Log.d("scroll","abc");
view2 = LayoutInflater.from(MainActivity.this).inflate(R.layout.education,null,false);
view2.setId(R.id.parent+position);
Animation rightAimation = new TranslateAnimation(view2.getX()+50,view2.getX(),view2.getY()+100,view2.getY());
rightAimation.setDuration(1000);
view2.startAnimation(rightAimation);
parent.addView(view2, params);
}
else
{
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.LEFT_OF,R.id.divider);
//params.topMargin = (position-1)*(view1.getLayoutParams().height)+position*100;
params.topMargin = 30;
params.rightMargin = 3;
params.addRule(RelativeLayout.BELOW,parent.getChildAt(position-1).getId());
Log.d("scroll","def");
view2 = LayoutInflater.from(MainActivity.this).inflate(R.layout.education,null,false);
view2.setId(R.id.parent+position);
Animation animation = new TranslateAnimation(view2.getX()-50,view2.getX(),view2.getY()+100,view2.getY());
animation.setDuration(1000);
view2.startAnimation(animation);
parent.addView(view2, params);
}
position++;
}
}
});
}
}
Thanks in advance.

Android doesn't render every pixel in a scroll, because it would be bad for performance. If you scroll really slow, you will see that your callback will be called for every pixel, but if you scroll fast, it will be called just when the render triggers.
In your case, you need to check if it rendered 50 or more pixels since last time you rendered the last view.

Related

Android addView and removeView in the same method

I'm calling this method that should redraw a pointer in the position given in every call.
ImageView ivPointer=null;
public void moveCursor(Bitmap bmPuntero, int x, int y)
{
RelativeLayout rl = (RelativeLayout) findViewById(R.id.gamelayout);
if (ivPointer!=null)
rl.removeView(ivPointer);
ivPointer = new ImageView(this);
ivPointer.setImageBitmap(bmPuntero);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(65, 65);
params.leftMargin = x;
params.topMargin = y;
rl.addView(ivPointer, params);
}
The result is that the bitmap isn't showed. If I remove the lines that remove the view, I see how the bitmap is drawn multiple times, so the add part should be correct.
Try this:
{
// Somewhere (in onCreate of the Activity for example):
RelativeLayout rl = (RelativeLayout) findViewById(R.id.gamelayout);
ImageView ivPointer = initPointer(this, bmPuntero); // Get image from somewhere
rl.addView(ivPointer);
}
// To update the cursor's po
public static void moveCursor(ImageView pointer, int x, int y) {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) pointer.getLayoutParams();
params.leftMargin = x;
params.topMargin = y;
pointer.setLayoutParams(params);
pointer.requestLayout(); // Refresh the layout
}
// Call this method to initialise the pointer (in onCreate of your Activity
// for example)
public static ImageView initPointer(Context context, Bitmap bmp) {
// Define the LayoutParams
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(65, 65);
params.leftMargin = DEFAULT_POS_X; // TODO: Constants to be defined
params.topMargin = DEFAULT_POS_Y;
// Init the ImageView
ImageView pointer = new ImageView(context);
pointer.setImageBitmap(bmp);
pointer.setLayoutParams(params);
return pointer;
}

How to handle simultaneous user fling and programmatic scroll in Android ScrollView

I have a LinearLayout inside a ScrollView. The LinearLayout has 100 FrameLayouts. When a button is clicked I insert new FrameLayouts in an in-between position from a BackgroundWorker. In order to maintain the position of the currently viewed FrameLayout I scroll the ScrollView to the offset that is equal to the current offset plus the height of the newly inserted FrameLayout.
While this process is running in the BackgroundWorker if the user flings the contents of the ScrollView with a positive velocity the same FrameLayout is coming again and again. For instance you start flinging with a small velocity from FrameLayout numbered 7, 8 appears from bottom but before appears fully again 7 comes. User scroll also works fine. The problem is with only fling.
I found that while flinging, in the OnScrollChanged event the oldt parameter is greater than t which is unexpected because the fling velocity is positive.
Below I insert new FrameLayouts from the 5th, 6th, 7th, 8th,.... positions in order.
public class MainActivity : Activity {
ScrollView1 sV;
Button btn;
LinearLayout linearLayout;
BackgroundWorker worker;
Handler handler;
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
sV = FindViewById<ScrollView1>(Resource.Id.scrollView1);
btn = FindViewById<Button>(Resource.Id.pagedownbutton);
btn.Click += Btn_Click;
sV.SetBackgroundColor(Android.Graphics.Color.White);
linearLayout = new LinearLayout(ApplicationContext);
linearLayout.Orientation = Orientation.Vertical;
worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
handler = new Handler();
for (int i = 0; i < 100; i++) {
TextView txt = new TextView(ApplicationContext);
txt.Text = i.ToString();
txt.TextSize = 50;
txt.SetTextColor(Color.White);
txt.Gravity = Android.Views.GravityFlags.Center;
FrameLayout frameLayout = new FrameLayout(ApplicationContext);
frameLayout.SetBackgroundColor(Android.Graphics.Color.Blue);
frameLayout.AddView(txt);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(700, 1200);
layoutParams.SetMargins(0, 5, 0, 0);
layoutParams.Gravity = Android.Views.GravityFlags.Center;
frameLayout.LayoutParameters = layoutParams;
linearLayout.AddView(frameLayout, i);
}
sV.AddView(linearLayout);
}
private void Worker_DoWork(object sender, DoWorkEventArgs e) {
for (int i = 0; i < 1000; i++) {
TextView txt = new TextView(ApplicationContext);
txt.Text = "i" + i.ToString();
txt.TextSize = 50;
txt.SetTextColor(Color.White);
txt.Gravity = Android.Views.GravityFlags.Center;
FrameLayout frameLayout = new FrameLayout(ApplicationContext);
frameLayout.SetBackgroundColor(Android.Graphics.Color.Blue);
frameLayout.AddView(txt);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(700, 1200);
layoutParams.SetMargins(0, 5, 0, 0);
layoutParams.Gravity = Android.Views.GravityFlags.Center;
frameLayout.LayoutParameters = layoutParams;
//Inducing some delay so that the background process will run long
for (int j = 0; j < 10000000; j++) {
}
//Adding FrameLayout and scrolling
handler.Post(() => {
linearLayout.AddView(frameLayout, 5 + i);
if (sV.ScrollY >= (5 + i) * 1205)
sV.ScrollTo(0, 1205 + sV.ScrollY);
});
}
}
private void Btn_Click(object sender, System.EventArgs e) {
worker.RunWorkerAsync();
}
}
public class ScrollView1 : ScrollView{
public ScrollView1(Context context):base(context) {
}
public ScrollView1(Android.Content.Context context, Android.Util.IAttributeSet attr) : base(context, attr) {
}
protected override void OnScrollChanged(int l, int t, int oldl, int oldt) {
}
public override void Fling(int velocityY) {
base.Fling(velocityY);
}
}
How can I make the FrameLayouts appear in order without scroll malfunctions while flinging with the programmatic scroll takes place in a background thread?

Android GridLayout only shows last child

I am new to Android Development. I have been working with using a GridLayout to display Dynamically inserted ImageViews.
My issue is located in "onFocusWindowChanged" but I pasted my onCreate where I do my assignments of the images.
private List<Behavior> behaviors = null;
private static int NUM_OF_COLUMNS = 2;
private List<ImageView> images;
private GridLayout grid;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_behaviors);
XMLPullParserHandler parser = new XMLPullParserHandler();
try {
behaviors = parser.parse(getAssets().open("catagories.xml"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
grid = (GridLayout) findViewById(R.id.behaviorGrid);
images = new ArrayList<ImageView>();
grid.setColumnCount(NUM_OF_COLUMNS);
grid.setRowCount(behaviors.size() / NUM_OF_COLUMNS);
for (Behavior behavior : behaviors)
images.add(this.getImageViewFromName(behavior.getName()));
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
View view = (View) findViewById(R.id.scrollView);
int width = (int) (view.getWidth() * .45);
Log.i("ViewWidth", Integer.toString(width));
GridLayout.LayoutParams lp = new GridLayout.LayoutParams();
lp.height = width;
lp.width = width;
int childCount = images.size();
ImageView image;
for (int i = 0; i < childCount-1; i++) {
image = images.get(i);
image.setLayoutParams(lp);
grid.addView(image);
}
}
In my (short) previous experience, using
grid.add(View);
worked fine, but now I am only seeing the last child display only. Looking through the debugger I can see that the gridview is being populated with more than just the last element, as well as the last imageview.
Thank you for your help
you should create a GridLayout.LayoutParams for each ImageView:
for (int i = 0; i < childCount-1; i++) {
GridLayout.LayoutParams lp = new GridLayout.LayoutParams();
lp.height = width;
lp.width = width;
......
}
GridLayout.LayoutParams contains location information, e.g [column:2, row:3]. In your code, all ImageViews are set the same GridLayout.LayoutParams, so they are located in the same cell(overlapping each other).
When use LinearLayout.LayoutParams instead, there is no location information in it. GridLayout will create a new GridLayout.LayoutParams for each child view, so all ImageViews use their own different GridLayout.LayoutParams and location.
Wish this help. You can read the GridLayout.java and ViewGroup.java for more details.
So I solved my issue, although I'm not sure how-
GridLayout.LayoutParams lp = new GridLayout.LayoutParams();
changed to...
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(x,y);
made it work just as I wanted it. But I'm not sure why- If anyone could explain, please do :)

Adding elements programmatically to FrameLayout using a for loop

Why when I add the elements to the FrameLayout flCatScroll only one shows and not ten are they perhaps overlapping and or why dosn't the margin increase.
myactivity.runOnUiThread(new Runnable() {
#Override
public void run() {
//Main Framelayout
FrameLayout flCatScroll = (FrameLayout) myactivity.findViewById(R.id.flCatScroll);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
params.gravity = Gravity.TOP;
params.topMargin = 20;
for(int i = 0; i < 10; i++) {
//FrameLayout mainItemLayout = new FrameLayout(myactivity);
listFramelayouts.add(new FrameLayout(myactivity));
}
for(int i = 0; i < listFramelayouts.size(); i++) {
params.topMargin = 20 * i;
TextView itemName = new TextView(myactivity);
itemName.setText("Test" + i);
listFramelayouts.get(i).addView(itemName);
flCatScroll.addView(listFramelayouts.get(i), params);
}
}
});
Reference semantics strike again!
Because you're reusing the params object instead of creating a new one each time, They all share the same margin at the end regardless of what the margin was when you added the view. So they are, indeed, all overlapping.
Does this make sense?
dcow is right. You are reusing the same LayoutParams object for each new TextView. So at the end all of them have the same topMargin. You need to create params per each new view. You can add new function like:
private FrameLayout.LayoutParams getLayoutParamsFor(final int i) {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
params.gravity = Gravity.TOP;
params.topMargin = 20 * i;
return params;
}
and then inside your loop you call it in addView(...) call:
flCatScroll.addView(listFramelayouts.get(i), getLayoutParamsFor(i));

Android get programmatically created view's width

I have TextView created programmatically, like that:
TextView mTextView = new TextView(getApplicationContext());
final LayoutParams params = new TableLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
((MarginLayoutParams) params).setMargins(8, 8, 0, 0);
mTextView.setText(mText);
mTextView.setLayoutParams(params);
mTextView.setPadding(2, 2, 2, 2);
mTextView.setBackground(getResources().getDrawable(R.drawable.note_corps));
tagMap.addView(mTextView);
textViewsWidth = mTextView.getWidth();
But mTextView.getWidth() always returns 0
And if I try:
mTextView.getLayoutParams().width
It returns the LayoutParams corresponding value in the LayoutParams class (-1 or -2)
How can I get the view's width ?
EDIT I need to do this here:
#Override
public void onPostExecute(Hashtable<String, Integer> hash){
final ScrollView tagMapScroll = (ScrollView) findViewById(R.id.tagMapScroll);
final LinearLayout tagMap = (LinearLayout) findViewById(R.id.tagMap);
final ArrayList<Integer> arr = new ArrayList<Integer>(hash.values());
final Enumeration<String> e = hash.keys();
int index = 0;
int textViewsWidth = 0;
while(e.hasMoreElements()){
final TextView tV = new TextView(getApplicationContext());
tV.setTextColor(Color.parseColor("#666666"));
tV.setText(Html.fromHtml(randomColor() + e.nextElement()));
tV.setTextSize(arr.get(index));
final LayoutParams params = new TableLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
((MarginLayoutParams) params).setMargins(8, 8, 0, 0);
tV.setLayoutParams(params);
tV.setPadding(2, 2, 2, 2);
tV.setBackground(getResources().getDrawable(R.drawable.note_corps));
tagMap.addView(tV);
textViewsWidth += tV.getWidth();
index++;
}
tagMapScroll.setVisibility(ScrollView.VISIBLE);
}
EDIT SOLUTION I used this from #androiduser's answer:
mTextView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width = mTextView.getMeasuredWidth();
int height = mTextView.getMeasuredHeight();
Problem solved !
I used this solution:
yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Ensure you call it only once
yourView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// Here you can get the size :)
}
});
get this way:
tV.post(new Runnable() {
#Override
public void run() {
int width=tV.getMeasuredWidth();
}
});
those values are constant value for FILL_PARENT and WRAP_CONTENT. If you want to check the view size you can try this way:
tagMap.addView(mTextView);
tagMap.post(new Runnable() {
#Override
public void run() {
mTextView. getWidth();
}
});
you have to wait until android draws the TextView. This way you are posting a Runnable in the tagMap queue, that is executed, hopefully after the textview is draw (so it should be weight and height)
In this method you can get the width height of the view..
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
int width = mTextView.getWidth();
int height = mTextView.getHeight();
}
and define TextView mTextView as global
You have to use ViewTreeObserver class which is used to register listeners that can be notified of global changes in the view tree. Such global events include, but are not limited to, layout of the whole tree, beginning of the drawing pass, touch mode change.
In the Activity's onCreate mehotd put this:
ViewTreeObserver vto = mTextView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mTextView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int viewWidth = mTextView.getMeasuredWidth();
}
});

Categories

Resources