Programmatically set constraints with between multiple same views - android

I have a constraintLayout that contains multiple nodeView's. A nodeView is a ImageView line attached to the left side of a ImageView circle
I now want to connect X amount of nodes together. To programmatically set constraints, you use the R.id, but since I'm connecting multiple same nodes together, and they all share the same R.id, this isn't working. Is there any way to reference a specific view's ImageView as a reference for setting a constraint for another ImageView? I'm starting to think I'm approaching this the wrong way entirely. Thanks.
EDIT: Here is the rest of the code.
node code
private void init(Context context, AttributeSet attrs, String description, boolean active, boolean base) {
View inflatedView = inflate(context, R.layout.tracking_node, this);
nodeLine = inflatedView.findViewById(R.id.imageNodeLine);
nodeImage = inflatedView.findViewById(R.id.imageNode);
nodeText = inflatedView.findViewById(R.id.textNode);
nodeLine.setId(View.generateViewId());
nodeImage.setId(View.generateViewId());
nodeText.setText(description);
if (active){
nodeImage.setImageResource(R.drawable.circle_green);
nodeLine.setImageResource(R.color.support_success);
}else{
nodeImage.setImageResource(R.drawable.circle_grey);
nodeImage.setImageResource(R.color.grey);
}
//Remove left-side connecting line if base node
if (base){
nodeLine.getLayoutParams().width = 20;
nodeLine.setImageResource(R.color.transparent);
}
}
public int getNodeImageId(){
return nodeImage.getId();
}
public int getNodeLineId(){
return nodeLine.getId();
}
constraintLayout code
private void init(Context context, AttributeSet attrs) {
View inflatedView = inflate(context, R.layout.delivery_status_view, this);
deliveryTrackerView = inflatedView.findViewById(R.id.linearLayoutDeliveryTracking);
shippingDetailsButton = inflatedView.findViewById(R.id.btnShippingDetails);
//steps[] is a string array that contains the content of each node
DeliveryNodeView node = new DeliveryNodeView(context, attrs, steps[0], true, true);
//Saves resource ID of last node image
int pastNodeID = node.getNodeImageId();
//Generates nodes
for (int i = 1; i < steps.length; i++){
boolean active = ((i + 1) / currentStep) <= 1;
node = new DeliveryNodeView(context, attrs, steps[i], active, false);
int nodeLineID = node.getNodeLineId();
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(deliveryTrackerView);
deliveryTrackerView.addView(node);
constraintSet.connect(nodeLineID, ConstraintSet.START, pastNodeID, ConstraintSet.END);
pastNodeID = node.getNodeImageId();
}
}

There are a few problems with your code. Here is some sample code that builds a 5x5 colored box array that looks like this:
The comments in the code outline the key steps. activity_main.xml is just an empty ConstraintLayout.
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ConstraintLayout layout = findViewById(R.id.layout);
int colorCounter = 0;
int idToTop = ConstraintSet.PARENT_ID;
int idToTopSide = ConstraintSet.TOP;
for (int i = 0; i < 5; i++) {
int idToLeft = ConstraintSet.PARENT_ID;
int idToLeftSide = ConstraintSet.START;
for (int j = 0; j < 5; j++) {
View box = getBox(colorCounter++ % 2 == 0);
// Add the view before getting the ConstraintSet.
layout.addView(box);
ConstraintSet cs = new ConstraintSet();
cs.clone(layout);
// Must constrain the view horizontally...
cs.connect(box.getId(), ConstraintSet.START, idToLeft, idToLeftSide);
//... and vertically.
cs.connect(box.getId(), ConstraintSet.TOP, idToTop, idToTopSide);
idToLeft = box.getId();
idToLeftSide = ConstraintSet.END;
// Apply the ConstraintSet to the layout.
cs.applyTo(layout);
}
idToTop = idToLeft;
idToTopSide = ConstraintSet.BOTTOM;
}
}
private View getBox(boolean isRed) {
View view = new View(this);
view.setId(View.generateViewId());
view.setBackgroundColor((isRed) ? Color.RED : Color.BLUE);
ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(200, 200);
view.setLayoutParams(lp);
return view;
}
}
Alternate code with the same result that separates out the view creation from making of the ConstraintSet connections. This may be a little more efficient.
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ConstraintLayout layout = findViewById(R.id.layout);
int colorCounter = 0;
int[][] connections = new int[5][5];
for (int row = 0; row < 5; row++) {
for (int col = 0; col < 5; col++) {
View box = getBox(colorCounter++ % 2 == 0);
// Add the view before getting the ConstraintSet.
layout.addView(box);
connections[row][col] = box.getId();
}
}
int idToTop = ConstraintSet.PARENT_ID;
int idToTopSide = ConstraintSet.TOP;
ConstraintSet cs = new ConstraintSet();
cs.clone(layout);
for (int i = 0; i < 5; i++) {
cs.connect(connections[i][0], ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START);
cs.connect(connections[i][0], ConstraintSet.TOP, idToTop, idToTopSide);
for (int j = 1; j < 5; j++) {
// Must constrain the view horizontally...
cs.connect(connections[i][j], ConstraintSet.START, connections[i][j - 1], ConstraintSet.END);
//... and vertically.
cs.connect(connections[i][j], ConstraintSet.TOP, idToTop, idToTopSide);
// Apply the ConstraintSet to the layout.
}
idToTop = connections[i][0];
idToTopSide = ConstraintSet.BOTTOM;
}
cs.applyTo(layout);
}
private View getBox(boolean isRed) {
View view = new View(this);
view.setId(View.generateViewId());
view.setBackgroundColor((isRed) ? Color.RED : Color.BLUE);
ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(200, 200);
view.setLayoutParams(lp);
return view;
}
}

Related

Programmatically building overlay layout after changing View.GONE to View.VISIBLE still doesn't get correct position

I'm trying to build a dynamic help overlay screen for my app. I have the basics working fine for the elements that are already visible or invisible, but when elements are initially Visibility.GONE (which I'm changing to visible before building the view) they still are found with position 0,0 and size 0x0.
I've tried adding short wait() loops, onLayoutChangedListener(), and getViewTreeObserver().addOnGlobalLayoutListener() which did nothing.
in onOptionsSelected():
HelpUtils helpUtils = new HelpUtils(mView, requireContext());
final View helpView = helpUtils.getHelpView();
final View mainLayout = helpUtils.getMainLayout();
View shiftFragment = mainLayout.findViewById(R.id.current_shift_layout);
Map<View, Integer> visibilityMap = new HashMap<>();
for (int i = 0; i < ((ViewGroup) shiftFragment).getChildCount(); i++) {
View child = ((ViewGroup)shiftFragment).getChildAt(i);
visibilityMap.put(child, child.getVisibility());
child.setVisibility(View.VISIBLE);
}
final ViewGroup viewGroup = helpUtils.getHelpViewGroup(targets);
if (firstTime) {
((FrameLayout)helpView).addView(viewGroup);
}
helpView.setVisibility(View.VISIBLE);
HelpUtils class:
public class HelpUtils {
final private View mView;
final private Context mContext;
public HelpUtils(View view, Context context) {
mView = view;
mContext = context;
}
public View getMainLayout() {
return mView.getRootView().findViewById(R.id.main_layout);
}
public View getHelpView() {
return getMainLayout().findViewById(R.id.help_view);
}
private void recursiveWalkViews(ViewGroup viewGroup, SparseArray<View> result, Set<Integer> targets) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View v = viewGroup.getChildAt(i);
if(v instanceof ViewGroup) {
recursiveWalkViews(((ViewGroup) v), result, targets);
} else {
if (targets.contains(v.getId())) {
result.put(v.getId(), v);
}
}
}
}
public SparseArray<View> getViewMap(Set<Integer> targets) {
SparseArray<View> map = new SparseArray<>();
ViewGroup view = (ViewGroup) mView;
recursiveWalkViews(view, map, targets);
return map;
}
private int getActionBarHeight() {
int[] textSizeAttr = new int[]{R.attr.actionBarSize};
TypedArray a = mContext.obtainStyledAttributes(new TypedValue().data, textSizeAttr);
int height = a.getDimensionPixelSize(0, 0);
a.recycle();
return height;
}
/**
* Match the layout of a new {#link TextView} to the layout of an existing view.
* #param source The {#link View} to be matched to.
* #param txt The text for the new {#link TextView}
* #return A new {#link TextView}
*/
public TextView matchParams(View source, String txt) {
int x, y;
float sp, size;
int[] location = new int[2];
int offset;
source.getLocationInWindow(location);
x = location[0];
y = location[1];
offset = source.getPaddingLeft();
size = ((TextView) source).getTextSize();
sp = size/mContext.getResources().getDisplayMetrics().scaledDensity;
TextView textView = new TextView(mContext);
textView.setWidth(source.getWidth() - source.getPaddingRight());
textView.setTextSize(14);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
if(source instanceof MaterialButton) {
params.topMargin = y + offset + ((MaterialButton)source).getPaddingBottom() - getActionBarHeight();
} else {
params.topMargin = y + offset - getActionBarHeight();
}
params.leftMargin = x + 8;
textView.setLayoutParams(params);
textView.setBackgroundColor(mContext.getResources().getColor(android.R.color.white));
textView.setText(txt);
return textView;
}
public ViewGroup getHelpViewGroup(Map<Integer, String> helpViews) {
FrameLayout layout = new FrameLayout(mContext);
SparseArray<View> views = getViewMap(helpViews.keySet());
List<TextView> textViews = new ArrayList<>(views.size());
for (int i = 0; i < views.size(); i++) {
View view = views.valueAt(i);
textViews.add(matchParams(views.valueAt(i), helpViews.get(views.keyAt(i))));
}
for (TextView textView : textViews) {
layout.addView(textView);
}
return layout;
}
}
Layout Inspector shows the overlay views are drawn at 0,0 with a width of 0, but the views that were changed to VISIBLE are in the correct place.
In onOptionsSelected() wrap your new view creation in a Runnable() from the target view (I'm guessing that's shiftFragment since that's where you're setting all the views to View.VISIBLE.
Basically make the following changes.
before:
for (int i = 0; i < ((ViewGroup) shiftFragment).getChildCount(); i++) {
View child = ((ViewGroup)shiftFragment).getChildAt(i);
visibilityMap.put(child, child.getVisibility());
child.setVisibility(View.VISIBLE);
}
final ViewGroup viewGroup = helpUtils.getHelpViewGroup(targets);
if (firstTime) {
((FrameLayout)helpView).addView(viewGroup);
}
helpView.setVisibility(View.VISIBLE);
after:
for (int i = 0; i < ((ViewGroup) shiftFragment).getChildCount(); i++) {
View child = ((ViewGroup)shiftFragment).getChildAt(i);
visibilityMap.put(child, child.getVisibility());
child.setVisibility(View.VISIBLE);
}
shiftFragment.post(new Runnable() {
#Override
public void run() {
final ViewGroup viewGroup = helpUtils.getHelpViewGroup(targets);
if (firstTime) {
((FrameLayout)helpView).addView(viewGroup);
}
helpView.setVisibility(View.VISIBLE);
);
The snippet below does the following:
Gets the parent view and posts a Runnable on the UI thread. This
ensures that the parent lays out its children before calling the
getHitRect() method. The getHitRect() method gets the child's hit
rectangle (touchable area) in the parent's coordinates.
from Manage touch events in a ViewGroup

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?

Change height of image button through code in android Studio

I am working in Android studio and using TableLayout. i want to display small imageButtons. and height of each button can be changed programmatically. I tried my best. but does not change the height of imageButton, Please anyone help me, what changes can i make in this code. thanks
public void settable() {
index = 0;
LinearLayout le = (LinearLayout) findViewById(R.id.onofffbutton);
float height = le.getHeight();
height= height/9.0f;
for (int a = 0; a < maxrow; a++) {
TableRow row = new TableRow(this);
for (int b = 0; b < col; b++) {
location[a][b] = new ImageView(this);
/*LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(50, 50);
location[a][b].setLayoutParams(layoutParams);*/
if (isOn[a][b]) {
location[a][b].setBackgroundResource(R.drawable.box1);
} else {
location[a][b].setBackgroundResource(R.drawable.box);
}
location[a][b].setMaxHeight(height);
location[a][b].setOnClickListener(Onclick);
location[a][b].setId(index++);
row.addView(location[a][b]);
}
tableLayout.addView(row, a);
}
}

How to set a Child View that spans multiple rows Programmatically

I'm getting an Illegal Argument Exception when running this Activity. Could someone point out why? My comment about the Specs will hopefully lead you to my misunderstanding of this code:
[Activity(Label = "Views/Layouts/GridLayout/3. Form (Java)", MainLauncher = true)]
[IntentFilter(new[] { Intent.ActionMain }, Categories = new string[] { })]
public class GridLayout3 : Activity {
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
SetContentView(Grid(this));
//SetContentView (Create (this));
}
public static View Grid(Context context) {
GridLayout gL = new GridLayout(context);
gL.AlignmentMode = GridAlign.Bounds;
gL.UseDefaultMargins = true;
//gL.ColumnOrderPreserved = false;
//gL.RowOrderPreserved = false;
gL.RowCount = 5;
gL.ColumnCount = 5;
//I want these Specs to tell the View to take up 2 rows of space.
//IE, (row 0-indexed) of 3 and 4 in column 4 should contain
//the same button
GridLayout.Spec specialRowSpec = GridLayout.InvokeSpec(3, 4);
GridLayout.Spec specialColSpec = GridLayout.InvokeSpec(4);
bool keyIsSpecial = false;
for (int i = 0; i < gL.RowCount; i++) {
GridLayout.Spec row = GridLayout.InvokeSpec(i);
for (int v = 0; v < gL.ColumnCount; v++) {
GridLayout.Spec col = GridLayout.InvokeSpec(v);
Button bt = new Button(context);
bt.Text = "Tester";
if (i == 3 && v == 4) {
keyIsSpecial = true;
}
if (keyIsSpecial) {
GridLayout.LayoutParams param = new GridLayout.LayoutParams();
param.RowSpec = specialRowSpec;
param.ColumnSpec = specialColSpec;
param.Height = 100;
gL.AddView(bt, new GridLayout.LayoutParams(param));
return gL;
} else {
gL.AddView(bt, new GridLayout.LayoutParams(row, col));
}
if (v == gL.ColumnCount - 1) {
v = 0;
break;
}
}
}
return gL;
}
The exception is thrown when I add the view with the special Specs: gL.AddView(bt, new GridLayout.LayoutParams(param));
Here's the link to the GridLayout.Spec documentation that I may be misunderstanding: http://developer.android.com/reference/android/widget/GridLayout.Spec.html
I've also tried exchanging the second argument to my specialRowSpec to use total size (height) instead of the index of the row. For example: GridLayout.Spec specialRowSpec = GridLayout.InvokeSpec(3, 2); This just gives me a 5 by 5 grid of buttons with no buttons spanning multiple rows.
Any questions?

ImageView pushed out from screen

I'm playing around in Android and what I'm trying to achieve is a 10x10tile boardgame.
I want to read size and width of screen and then I want a square in the middle with a textViews above and below.
This is what I've done so far:
public void init(){
Point size = new Point();
WindowManager w = getWindowManager();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
w.getDefaultDisplay().getSize(size);
screenWidth = size.x;
screenHeight = size.y;
}else{
Display d = w.getDefaultDisplay();
screenWidth = d.getWidth();
screenHeight = d.getHeight();
}
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.LinearLayout);
linearLayout.setOrientation(LinearLayout.VERTICAL);
TextView TVtop = new TextView(this.getApplicationContext());
TVtop.setHeight((screenHeight-screenWidth)/2);
TVtop.setWidth(screenWidth);
TVtop.setText("TOP");
TextView TVbot = new TextView(this.getApplicationContext());
TVbot.setHeight((screenHeight-screenWidth)/2);
TVbot.setWidth(screenWidth);
TVbot.setText("BOT");
TableLayout tableLayout = new TableLayout(this.getApplicationContext());
//Make a cube
LayoutParams tableParams = new LayoutParams(screenWidth, screenWidth);
tableLayout.setLayoutParams(tableParams);
//Populate tableLayout
for(int i = 0; i < nrOfTiles; i++){
TableRow tableRow = new TableRow(this.getApplicationContext());
for(int j = 0; j < nrOfTiles; j++){
ImageView imgView = new ImageView(this.getApplicationContext());
imgView.setImageResource(R.drawable.cell);
tableRow.addView(imgView);
}
tableLayout.addView(tableRow);
}
linearLayout.addView(TVtop);
linearLayout.addView(tableLayout);
linearLayout.addView(TVbot);
}
}
I've tried diffrent layoutparams but nothing seems to get the work done. :S
BR
Personally, I would use a RelativeLayout and setMargins for the items.
While not a direct answer to your question, the following code will display 15 icons in three rows. This should be enough to explain and get you started.
The main activity.
public class MainActivity extends Activity {
private int mWidth;
private int mTile;
private int mColMax = 5;
private Context mContext;
#SuppressWarnings("deprecation")
#SuppressLint("NewApi")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
// the screen width is need to work out the tile size
WindowManager w = getWindowManager();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
Point size = new Point();
w.getDefaultDisplay().getSize(size);
mWidth = size.x;
}else{
mWidth = w.getDefaultDisplay().getWidth();
}
// how wide (and high) each icon will be to fit the screen.
mTile = (mWidth / mColMax);
setContentView(R.layout.activity_main);
// layout the icons
initUI();
}
/**
* Layout 15 icon images in three rows dynamically.
*/
private void initUI() {
// this is the layout from the XML
ViewGroup layout = (ViewGroup) findViewById(R.id.main_layout);
ImageView iv;
RelativeLayout.LayoutParams params;
int i = 0;
int row = 0;
int col = 0;
do {
params = new RelativeLayout.LayoutParams(mTile,mTile);
params.setMargins((col * mTile), (row * mTile), 0, 0);
iv = new ImageView(mContext);
iv.setAdjustViewBounds(true);
iv.setScaleType(ScaleType.FIT_CENTER);
iv.setImageResource(R.drawable.ic_launcher);
iv.setLayoutParams(params);
layout.addView(iv);
if (col == mColMax) {
row++;
col = 0;
} else {
col++;
}
} while (++i <= 16);
}
}
And the layout XML.
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>

Categories

Resources