Showing posts with label android-view. Show all posts
Showing posts with label android-view. Show all posts

Saturday, September 8, 2018

How to place elements side by side each other, programmatically?

Leave a Comment

I have a spinner, checkbox, and eddittext. Currently they are all one under each other but I want to make it so they are side by side each other

For example:

the spinner on the left, the edittext on the right of the spinner and then the checkbox on the right of the edittext.

Is this possible to do this programatically because these elements are created programatically.

xml layout

<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     xmlns:tools="http://schemas.android.com/tools"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:background="@color/colorBackground"     android:minHeight="170dp"     tools:context=".create"     tools:layout_editor_absoluteY="81dp"     >       <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"         android:id="@+id/scrollView2"         android:layout_width="match_parent"         android:layout_height="438dp"         android:fillViewport="true"         app:layout_constraintEnd_toEndOf="parent"         app:layout_constraintHorizontal_bias="1.0"         app:layout_constraintStart_toStartOf="parent"         tools:layout_editor_absoluteY="0dp">           <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"             xmlns:app="http://schemas.android.com/apk/res-auto"             xmlns:tools="http://schemas.android.com/tools"             android:id="@+id/my_linearLayout"             android:layout_width="match_parent"             android:layout_height="match_parent"             android:orientation="vertical" >               <LinearLayout                 android:layout_width="match_parent"                 android:layout_height="wrap_content"                 android:orientation="vertical"                 android:id="@+id/verticalLayout"                 android:layout_below="@id/view2">              </LinearLayout>           </LinearLayout>           </ScrollView>      <android.support.design.widget.FloatingActionButton         android:id="@+id/fab"         android:layout_width="60dp"         android:layout_height="70dp"         android:layout_gravity="bottom|end"         android:layout_marginBottom="16dp"         android:layout_marginEnd="16dp"         android:layout_marginLeft="8dp"         android:layout_marginRight="16dp"         android:layout_marginStart="8dp"         android:src="@android:drawable/ic_input_add"         app:backgroundTint="@color/colorCreate"         app:elevation="6dp"         app:layout_constraintBottom_toBottomOf="parent"         app:layout_constraintEnd_toEndOf="parent"         app:layout_constraintHorizontal_bias="1.0"         app:layout_constraintStart_toStartOf="parent"         app:pressedTranslationZ="12dp"         android:tint="@color/colorBackground"/>       <View         android:id="@+id/subheading"         android:layout_width="match_parent"         android:layout_height="83dp"         android:layout_marginBottom="8dp"         android:layout_marginEnd="1dp"         android:layout_marginLeft="1dp"         android:layout_marginRight="1dp"         android:layout_marginStart="1dp"         android:layout_marginTop="2dp"         android:background="@color/colorBackground"         app:layout_constraintBottom_toBottomOf="parent"         app:layout_constraintEnd_toEndOf="parent"         app:layout_constraintHorizontal_bias="0.0"         app:layout_constraintStart_toStartOf="parent"         app:layout_constraintTop_toTopOf="parent"         app:layout_constraintVertical_bias="0.0"         />      <View         android:id="@+id/view"         android:layout_width="320dp"         android:layout_height="1dp"         android:layout_marginEnd="8dp"         android:layout_marginLeft="8dp"         android:layout_marginRight="8dp"         android:layout_marginStart="8dp"         android:layout_marginTop="76dp"         android:background="@color/colorText"         app:layout_constraintEnd_toEndOf="parent"         app:layout_constraintStart_toStartOf="parent"         app:layout_constraintTop_toTopOf="parent" />      <View         android:id="@+id/view2"         android:layout_width="320dp"         android:layout_height="1dp"         android:layout_marginEnd="8dp"         android:layout_marginLeft="8dp"         android:layout_marginRight="8dp"         android:layout_marginStart="8dp"         android:layout_marginTop="28dp"         android:background="@color/colorText"         app:layout_constraintEnd_toEndOf="parent"         app:layout_constraintStart_toStartOf="parent"         app:layout_constraintTop_toTopOf="parent" />      <TextView         android:id="@+id/textView"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_marginEnd="8dp"         android:layout_marginLeft="8dp"         android:layout_marginRight="8dp"         android:layout_marginStart="8dp"          android:layout_marginTop="12dp"         android:fontFamily="@font/droid_sans"         android:text="@string/done_label"         android:textColor="@color/colorText"         android:textSize="20sp"         app:layout_constraintEnd_toEndOf="parent"         app:layout_constraintStart_toEndOf="@+id/textView4"         app:layout_constraintTop_toBottomOf="@+id/view2" />      <TextView         android:id="@+id/textView4"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_marginBottom="8dp"         android:layout_marginEnd="128dp"         android:layout_marginRight="128dp"         android:layout_marginTop="8dp"         android:fontFamily="@font/droid_sans"         android:text="@string/aisle_label"         android:textColor="@color/colorText"         android:textSize="20sp"         app:layout_constraintBottom_toTopOf="@+id/view"         app:layout_constraintEnd_toEndOf="parent"         app:layout_constraintTop_toBottomOf="@+id/view2"         app:layout_constraintVertical_bias="1.0" />      <TextView         android:id="@+id/textView3"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_marginBottom="5dp"         android:layout_marginEnd="32dp"         android:layout_marginRight="32dp"         android:layout_marginTop="5dp"         android:fontFamily="@font/droid_sans"         android:text="@string/qty_label"         android:textColor="@color/colorText"         android:textSize="20sp"         app:layout_constraintBottom_toTopOf="@+id/view"         app:layout_constraintEnd_toStartOf="@+id/textView4"         app:layout_constraintTop_toBottomOf="@+id/view2"         app:layout_constraintVertical_bias="0.7" />      <TextView         android:id="@+id/textView2"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_marginBottom="8dp"         android:layout_marginEnd="8dp"         android:layout_marginLeft="8dp"         android:layout_marginRight="8dp"         android:layout_marginStart="8dp"         android:layout_marginTop="8dp"         android:fontFamily="@font/droid_sans"         android:text="@string/item_label"         android:textColor="@color/colorText"         android:textSize="20sp"         app:layout_constraintBottom_toTopOf="@+id/view"         app:layout_constraintEnd_toStartOf="@+id/textView3"         app:layout_constraintStart_toStartOf="parent"         app:layout_constraintTop_toBottomOf="@+id/view2"         app:layout_constraintVertical_bias="1.0" />   </android.support.constraint.ConstraintLayout> 

java code

public class create extends AppCompatActivity {       private LinearLayout mLinearLayout;     private ArrayList<SearchableSpinner> mSpinners;     private List<AppCompatButton> mButtons = new ArrayList<>();     private List<CheckBox> mCheckboxes = new ArrayList<>();     private List<TextView> mTextviews = new ArrayList<>();     private List<EditText> mEdittexts = new ArrayList<>();     private List<View> mViews = new ArrayList<>();     private Map<String, String> numberItemValues = new HashMap<>();     private Spinner spinner;     private TextView textview;     private CheckBox checkbox;     List<String> itemList = new ArrayList<>();     LinearLayout verticallayout,horizontalLayout;           @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_create);         getSupportActionBar().setDisplayHomeAsUpEnabled(true);           //Arrays         ArrayList<String> items = new ArrayList<>();         items.add(String.valueOf(mSpinners)); // add you selected item         mSpinners = new ArrayList<>();             //Initializing Variables Here         mLinearLayout = findViewById(R.id.my_linearLayout);         verticallayout = findViewById(R.id.verticalLayout);         textview = findViewById(R.id.textview);         checkbox = findViewById(R.id.checkbox);              //code for the add button to add more items         FloatingActionButton floatingActionButton =                 (FloatingActionButton) findViewById(R.id.fab);           floatingActionButton.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View view) {                 Toast.makeText(getBaseContext(), "Item added!", Toast.LENGTH_SHORT).show();                 spinner = findViewById(R.id.spinner);                   createRow();                    // Handle ze click.              /*   final Spinner spinner = makeSpinner();                 mLinearLayout.addView(spinner);                   LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) spinner.getLayoutParams();                 layoutParams.setMargins(5, 100, 10, 0); //top 70                  Resources resources = getResources();                 DisplayMetrics metrics = resources.getDisplayMetrics();                  layoutParams.height = (int) (70 * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)); //80                 layoutParams.width = (int) (240 * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)); //240                 spinner.setLayoutParams(layoutParams);*/                  final View newView = makeView();                 //Add a new view                 mLinearLayout.addView(newView);                 mViews.add(newView);  /*                  final EditText newEdittext = makeEdittext();                 mLinearLayout.addView(newEdittext);                 mEdittexts.add(newEdittext);   */                   final int listSize = mViews.size();                   //code for deleting the said item.                 newView.setOnClickListener(new View.OnClickListener() {                     //start                     @Override                     public void onClick(View view) {                          //when the 'new button' is pressed, alert shows if you are sure you want to delete the item or not.                          final View.OnClickListener context = this;                           AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(create.this);                           // set title                         alertDialogBuilder.setTitle("Delete Item");                          // set dialog message                         alertDialogBuilder                                 .setMessage("Are you sure you want to delete this item?")                                 .setCancelable(false)                                 .setPositiveButton("Yes", new DialogInterface.OnClickListener() {                                     public void onClick(DialogInterface dialog, int id) {                                         // if this button is clicked, close                                         // current activity                                           if (listSize > 0) {                                              mCheckboxes.get(listSize - 1).setVisibility(View.GONE);                                             mSpinners.get(listSize - 1).setVisibility(View.GONE);                                             mViews.get(listSize - 1).setVisibility(View.GONE);                                             mTextviews.get(listSize - 1).setVisibility(View.GONE);                                             mEdittexts.get(listSize - 1).setVisibility(View.GONE);                                             Toast.makeText(getBaseContext(), "Item removed.", Toast.LENGTH_SHORT).show();                                          }                                       }                                 })                                 .setNegativeButton("No", new DialogInterface.OnClickListener() {                                     public void onClick(DialogInterface dialog, int id) {                                         // if this button is clicked, just close                                         // the dialog box and do nothing                                         dialog.cancel();                                     }                                 });                          // create alert dialog                         AlertDialog alertDialog = alertDialogBuilder.create();                          // show it                         alertDialog.show();                       }                 });               /*    //Add a new checkbox                 final CheckBox newCheckbox = makeCheckbox();                 mLinearLayout.addView(newCheckbox);                  //TODO add checkbox to your list                 mCheckboxes.add(newCheckbox);*/                   final TextView newTextview = makeTextview();                 mLinearLayout.addView(newTextview);                 mTextviews.add(newTextview);                  //TODO Add the spinner on item selected listener to get selected items                 spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {                     @Override                     public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {                         String currentItem = itemList.get(position);                         String aisleNumber = numberItemValues.get(currentItem);                         //TODO you can use the above aisle number to add to your text view                         //mTextviews.get(mTextviews.size() -1).setText(aisleNumber);                         newTextview.setText(aisleNumber);                     }                      @Override                     public void onNothingSelected(AdapterView<?> parentView) {                         //  code here                     }                  });               }         });       }       public void createRow(View view){          createRow(); //got      }       private void createRow() { //got          horizontalLayout = new LinearLayout(this);          LinearLayout.LayoutParams horizontalParams = new LinearLayout.LayoutParams(                 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);         horizontalLayout.setOrientation(LinearLayout.HORIZONTAL);         horizontalLayout.setLayoutParams(horizontalParams);          makeEdittext();         makeCheckbox();         makeSpinner();          verticallayout.addView(horizontalLayout);     }               //TODO OVERFLOW MENU     @Override     public boolean onCreateOptionsMenu(Menu menu) {         // Inflate the menu; this adds items to the action bar if it is present.         getMenuInflater().inflate(R.menu.create_menu, menu);         return true;     }      @Override     public boolean onOptionsItemSelected(MenuItem item) {          int id = item.getItemId();          if (id == R.id.action_settings) {             Intent startSettingsActivity = new Intent(this, AppCompatPreferenceActivity.class);             startActivity(startSettingsActivity);             //start the settings activity here.             return true;         }           else if (id == R.id.action_delete) {             //code to delete list here.             return true;          }          else if (id == R.id.action_gotoview) {               //takes the size of the list of spinners, for reference             ArrayList<Integer> spinnerList = new ArrayList<>();             for(int i = 0; i < mSpinners.size(); i++) {                 spinnerList.add(mSpinners.get(i).getSelectedItemPosition());               }             Bundle newBundle = new Bundle();             newBundle.putIntegerArrayList("arraylist", spinnerList);             Intent newIntent = new Intent(create.this, view.class);             newIntent.putExtra("extraBundle", newBundle);              startActivity(newIntent);                //code to go to view screen, aka intent             return true;         }            return super.onOptionsItemSelected(item);     }          //use a relative layout and specify which ones are to layout_toRightOf and layout_below      //DUPLICATING ITEMS WHEN FAB IS PRESSED//     private CheckBox makeCheckbox() {         //Create new Checkbox         CheckBox checkbox = new CheckBox(this);          // Setup layout           LinearLayout.LayoutParams checkParams = new LinearLayout.LayoutParams(                 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);         checkParams.setMargins(10, 10, 10, 10);         checkParams.gravity = Gravity.LEFT;          checkbox.setLayoutParams(checkParams);         horizontalLayout.addView(checkbox);           return checkbox;     }        private TextView makeTextview() {         //create new textview         TextView textview = new TextView(this);          //setup layout          LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(                 LinearLayout.LayoutParams.MATCH_PARENT,                 LinearLayout.LayoutParams.WRAP_CONTENT);         textview.setLayoutParams(layoutParams);         textview.setTextSize(30);          return textview;     }       private EditText makeEdittext() {         //create new edittext         EditText edittext = new EditText(this);         /* //setup layout         final LinearLayout.LayoutParams lparams = new LinearLayout.LayoutParams(70, 50); // Width , height         edittext.setLayoutParams(lparams);*/           LinearLayout.LayoutParams editParams = new LinearLayout.LayoutParams(                 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);         editParams.setMargins(10, 10, 10, 10);         editParams.width = 70;         editParams.height = 50;           edittext.setLayoutParams(editParams);         horizontalLayout.addView(edittext);            edittext.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED);         edittext.setHint("qty");          return edittext;      }         private View makeView() {         //create new View          View view = new View(this);         view.setBackgroundColor(Color.parseColor("#ffffff"));         LinearLayout.LayoutParams layoutParams =  new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, 100);         new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, 50);         //LinearLayout.LayoutParams.MATCH_PARENT,         // LinearLayout.LayoutParams.WRAP_CONTENT);         view.setClickable(true);           view.setLayoutParams(layoutParams);           //setup layout          return view;       }           private Spinner makeSpinner() {         //opens csv         InputStream inputStream = getResources().openRawResource(R.raw.shopitems);         CSVFile csvFile = new CSVFile(inputStream);         //TODO I made this variable global, declared it at the very top of this file         itemList = csvFile.read();          //Create new spinner         // SearchableSpinner spinner = (SearchableSpinner) new Spinner(this, Spinner.MODE_DROPDOWN);         SearchableSpinner spinner = new SearchableSpinner(this);           // Setup layout          LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams(                 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);         spinnerParams.setMargins(10, 10, 10, 10);         spinnerParams.gravity = Gravity.LEFT;          Resources resources = getResources();         DisplayMetrics metrics = resources.getDisplayMetrics();          spinnerParams.height = (int) (70 * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)); //80         spinnerParams.width = (int) (240 * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)); //240            spinner.setLayoutParams(spinnerParams);         horizontalLayout.addView(spinner);          MyListAdapter adapter = new MyListAdapter(this, R.layout.listrow, R.id.txtid, itemList);           spinner.setAdapter(adapter);          //Add it to your list of spinners so you can retrieve their data when you click the getSpinner button         mSpinners.add(spinner);         return spinner;     }        private class CSVFile {         InputStream inputStream;          public CSVFile(InputStream inputStream) {             this.inputStream = inputStream;         }          public List<String> read() {              List<String> resultList = new ArrayList<String>();             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));             try {                 String line;                 while ((line = reader.readLine()) != null) {                     String[] row = line.split(",");                     //TODO I edited this part so that you'd add the values in our new hash map variable                     numberItemValues.put(row[1], row[0]);                     resultList.add(row[1]);                 }             } catch (IOException e) {                 Log.e("Main", e.getMessage());             } finally {                 try {                     inputStream.close();                 } catch (IOException e) {                     Log.e("Main", e.getMessage());                 }             }             return resultList;         }     }} 

I get an error on this line:

spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

error:

E/AndroidRuntime: FATAL EXCEPTION: main java.lang.NullPointerException

4 Answers

Answers 1

Set your linear layout orientation to horizontal

<LinearLayout         xmlns:android="http://schemas.android.com/apk/res/android"         xmlns:app="http://schemas.android.com/apk/res-auto"         xmlns:tools="http://schemas.android.com/tools"         android:id="@+id/my_linearLayout"         android:layout_width="match_parent"         android:layout_height="match_parent"         android:orientation="horizontal"> 

Answers 2

Try this way

home_activity.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:orientation="vertical">      <Button         android:id="@+id/button"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_gravity="center_horizontal"         android:onClick="createRow"         android:text="Create Row" />      <LinearLayout         android:id="@+id/verticalLayout"         android:layout_width="match_parent"         android:layout_height="match_parent"         android:orientation="vertical">      </LinearLayout> </LinearLayout> 

HomeActivity.java

public class HomeActivity extends AppCompatActivity {     LinearLayout verticallayout, horizontalLayout;     int width;       @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.home_activity);         verticallayout = findViewById(R.id.verticalLayout);         DisplayMetrics displayMetrics = new DisplayMetrics();         getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);         width = displayMetrics.widthPixels;      }      public void createRow(View view) {         createRow();     }      private void createRow() {         horizontalLayout = new LinearLayout(this);         LinearLayout.LayoutParams horizontalParams = new LinearLayout.LayoutParams(                 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);         horizontalLayout.setOrientation(LinearLayout.HORIZONTAL);         horizontalLayout.setLayoutParams(horizontalParams);          createSpinner();         createCheckbox();         createEditText();          verticallayout.addView(horizontalLayout);     }      private void createEditText() {         EditText editText = new EditText(this);          LinearLayout.LayoutParams editParams = new LinearLayout.LayoutParams((width/3), convertDpToPixel(40));         editParams.setMargins(10, 10, 10, 10);          editText.setLayoutParams(editParams);         editText.setHint("Enter something");         editText.setTextSize(14f);         horizontalLayout.addView(editText);     }       private void createCheckbox() {         CheckBox checkBox = new CheckBox(this);          LinearLayout.LayoutParams checkParams = new LinearLayout.LayoutParams((width/3), convertDpToPixel(40));         checkParams.setMargins(10, 10, 10, 10); //        checkParams.gravity = Gravity.LEFT;          checkBox.setLayoutParams(checkParams);         checkBox.setText("Is Applicable");         horizontalLayout.addView(checkBox);      }      private void createSpinner() {         Spinner spinner = new Spinner(this);         LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams((width/3), convertDpToPixel(40));;         spinnerParams.setMargins(10, 10, 10, 10); //        spinnerParams.gravity = Gravity.LEFT;          spinner.setLayoutParams(spinnerParams);         String[] item = new String[]{"item1","item2","item3","item4","item5","item6","item7"};         ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_dropdown_item, item);         spinner.setAdapter(spinnerArrayAdapter);         spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {             @Override             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {                 System.out.println(parent.getSelectedItem().toString());             }              @Override             public void onNothingSelected(AdapterView<?> parent) {              }         });          horizontalLayout.addView(spinner);     }       public int convertDpToPixel(int dp) {         DisplayMetrics metrics = getResources().getDisplayMetrics();         return dp * ((int) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);     }  } 

may be this code generate some margin problem, but u can solve it by managing the value of Params.setMargins(10, 10, 10, 10)

let me know if any concern.

Answers 3

I was able to achieve this by using a static Linear Layout in XML ( Vertical orientation ) and added horizontal rows of the elements dynamically.

layout.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout     xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent"     android:layout_height="match_parent">     <Button         android:id="@+id/button"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_alignParentTop="true"         android:layout_centerHorizontal="true"         android:gravity="right"         android:onClick="createRow"         android:text="Create Row" />      <LinearLayout         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:orientation="vertical"         android:id="@+id/verticalLayout"         android:layout_below="@id/button">      </LinearLayout> </RelativeLayout> 

Activity to add Layout Dynamically

public class MyLayoutActivity extends AppCompatActivity {      LinearLayout verticallayout,horizontalLayout;      @Override     protected void onCreate(@Nullable Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.layout_poc);          verticallayout = findViewById(R.id.verticalLayout);      }      public void createRow(View view){          createRow();      }      private void createRow() {          horizontalLayout = new LinearLayout(this);          LinearLayout.LayoutParams horizontalParams = new LinearLayout.LayoutParams(                 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);         horizontalLayout.setOrientation(LinearLayout.HORIZONTAL);         horizontalLayout.setLayoutParams(horizontalParams);          createSpinner();         createCheckbox();         createEditText();          verticallayout.addView(horizontalLayout);     }      private void createEditText() {         EditText editText = new EditText(this);          LinearLayout.LayoutParams editParams = new LinearLayout.LayoutParams(                 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);         editParams.setMargins(10, 10, 10, 10);          editText.setLayoutParams(editParams);         horizontalLayout.addView(editText);     }       private void createCheckbox() {         CheckBox checkBox = new CheckBox(this);          LinearLayout.LayoutParams checkParams = new LinearLayout.LayoutParams(                 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);         checkParams.setMargins(10, 10, 10, 10);         checkParams.gravity = Gravity.LEFT;          checkBox.setLayoutParams(checkParams);         horizontalLayout.addView(checkBox);      }      private void createSpinner() {          Spinner spinner = new Spinner(this);         //ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_dropdown_item, spinnerArray);         //spinner.setAdapter(spinnerArrayAdapter);         LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams(                 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);         spinnerParams.setMargins(10, 10, 10, 10);         spinnerParams.gravity = Gravity.LEFT;          spinner.setLayoutParams(spinnerParams);         horizontalLayout.addView(spinner);     } } 

Add the data to your spinner in array adapter according to your logic. you can change Layout params as per your design. My code gives output like below image:

Output

Answers 4

as other answers are suggesting you can always create views dynamically. but what I think you should do since you haven't accept any of these answers is to create your desired XML row and then you can add that view to your layout

LayoutInflater inflater = LayoutInflater.from(context); View v = inflater.inflate(R.layout.yourxmlrow, null, false); linearLayout.addView(v); 
Read More

Friday, August 10, 2018

android.graphics draw a line from one View pointing to another View

Leave a Comment

I know android.graphics is old, but i am having trouble doing a simple stuff.

I want to draw a line animation where one View points an arrow/line into another View

First Button-------------------------------->Second Button

I have tried creating a custom View class and overriding the onDraw(Canvas c) method and then using the drawLine(startX, startY, stopX, stopY, paint) method from the Canvas Object. But i don't know which coordinates to get in order to point one View to the other View

I don't want to create a static View in the XML layout with a slim height because the View can be added dynamically by the user, which i think drawing the line dynamically is the best way.

Please help me out. Thank you!

2 Answers

Answers 1

Use Path and Pathmeasure for Drawing Animated Line. I have Made and test it.

Make Custom View and pass view coordinates points array to it,

public class AnimatedLine extends View {     private final Paint mPaint;     public Canvas mCanvas;     AnimationListener animationListener;      Path path;     private static long animSpeedInMs = 2000;     private static final long animMsBetweenStrokes = 100;     private long animLastUpdate;     private boolean animRunning = true;     private int animCurrentCountour;     private float animCurrentPos;     private Path animPath;     private PathMeasure animPathMeasure;      float pathLength;       float distance = 0;     float[] pos;     float[] tan;     Matrix matrix;     Bitmap bm;       public AnimatedLine(Context context) {         this(context, null);         mCanvas = new Canvas();     }      public AnimatedLine(Context context, AttributeSet attrs) {         super(context, attrs);         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);         mPaint.setStyle(Paint.Style.STROKE);         mPaint.setStrokeWidth(15);         mPaint.setStrokeCap(Paint.Cap.ROUND);         mPaint.setStrokeJoin(Paint.Join.ROUND);         mPaint.setColor(context.getResources().getColor(R.color.materialcolorpicker__red));           if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {             setLayerType(LAYER_TYPE_SOFTWARE, mPaint);         }         bm = BitmapFactory.decodeResource(getResources(), R.drawable.hand1);         bm = Bitmap.createScaledBitmap(bm, 20,20, false);         distance = 0;         pos = new float[2];         tan = new float[2];          matrix = new Matrix();     }      @Override     protected void onDraw(Canvas canvas) {         super.onDraw(canvas);          mCanvas = canvas;          if (path != null) {              if (animRunning) {                 drawAnimation(mCanvas);             } else {                 drawStatic(mCanvas);             }          }      }       /**      * draw Path With Animation      *      * @param time in milliseconds      */     public void drawWithAnimation(ArrayList<PointF> points, long time,AnimationListener animationListener) {         animRunning = true;         animPathMeasure = null;         animSpeedInMs = time;         setPath(points);         setAnimationListener(animationListener);         invalidate();     }      public void setPath(ArrayList<PointF> points) {         if (points.size() < 2) {             throw new IllegalStateException("Pass atleast two points.");         }         path = new Path();         path.moveTo(points.get(0).x, points.get(0).y);         path.lineTo(points.get(1).x, points.get(1).y);     }      private void drawAnimation(Canvas canvas) {         if (animPathMeasure == null) {             // Start of animation. Set it up.             animationListener.onAnimationStarted();             animPathMeasure = new PathMeasure(path, false);             animPathMeasure.nextContour();             animPath = new Path();             animLastUpdate = System.currentTimeMillis();             animCurrentCountour = 0;             animCurrentPos = 0.0f;              pathLength = animPathMeasure.getLength();           } else {             // Get time since last frame             long now = System.currentTimeMillis();             long timeSinceLast = now - animLastUpdate;              if (animCurrentPos == 0.0f) {                 timeSinceLast -= animMsBetweenStrokes;             }              if (timeSinceLast > 0) {                 // Get next segment of path                 float newPos = (float) (timeSinceLast) / (animSpeedInMs / pathLength) + animCurrentPos;                 boolean moveTo = (animCurrentPos == 0.0f);                 animPathMeasure.getSegment(animCurrentPos, newPos, animPath, moveTo);                 animCurrentPos = newPos;                 animLastUpdate = now;                   //start draw bitmap along path                 animPathMeasure.getPosTan(newPos, pos, tan);                 matrix.reset();                 matrix.postTranslate(pos[0], pos[1]);                 canvas.drawBitmap(bm, matrix, null);                 //end drawing bitmap                    //take current position                 animationListener.onAnimationUpdate(pos);                  // If this stroke is done, move on to next                 if (newPos > pathLength) {                     animCurrentPos = 0.0f;                     animCurrentCountour++;                     boolean more = animPathMeasure.nextContour();                     // Check if finished                     if (!more) {                         animationListener.onAnimationEnd();                         animRunning = false;                     }                 }             }              // Draw path             canvas.drawPath(animPath, mPaint);          }          invalidate();     }      private void drawStatic(Canvas canvas) {         canvas.drawPath(path, mPaint);         canvas.drawBitmap(bm, matrix, null);     }       public void setAnimationListener(AnimationListener animationListener) {         this.animationListener = animationListener;     }        public interface AnimationListener {         void onAnimationStarted();          void onAnimationEnd();          void onAnimationUpdate(float[] pos);     } } 

Answers 2

For drawing lines between views better if all of it lays on same parent layout. For the conditions of the question (Second Button is exactly to the right of First Button) you can use custom layout like that:

public class ArrowLayout extends RelativeLayout {      public static final String PROPERTY_X = "PROPERTY_X";     public static final String PROPERTY_Y = "PROPERTY_Y";      private final static double ARROW_ANGLE = Math.PI / 6;     private final static double ARROW_SIZE = 50;      private Paint mPaint;      private boolean mDrawArrow = false;     private Point mPointFrom = new Point();   // current (during animation) arrow start point     private Point mPointTo = new Point();     // current (during animation)  arrow end point      public ArrowLayout(Context context) {         super(context);         init();     }      public ArrowLayout(Context context, AttributeSet attrs) {         super(context, attrs);         init();     }      public ArrowLayout(Context context, AttributeSet attrs, int defStyleAttr) {         super(context, attrs, defStyleAttr);         init();     }      public ArrowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {         super(context, attrs, defStyleAttr, defStyleRes);         init();     }      private void init() {         setWillNotDraw(false);         mPaint = new Paint();         mPaint.setStyle(Paint.Style.STROKE);         mPaint.setAntiAlias(true);         mPaint.setColor(Color.BLUE);         mPaint.setStrokeWidth(5);     }      @Override     public void dispatchDraw(Canvas canvas) {         super.dispatchDraw(canvas);         canvas.save();         if (mDrawArrow) {             drawArrowLines(mPointFrom, mPointTo, canvas);         }         canvas.restore();     }      private Point calcPointFrom(Rect fromViewBounds, Rect toViewBounds) {         Point pointFrom = new Point();          pointFrom.x = fromViewBounds.right;         pointFrom.y = fromViewBounds.top + (fromViewBounds.bottom - fromViewBounds.top) / 2;          return pointFrom;     }       private Point calcPointTo(Rect fromViewBounds, Rect toViewBounds) {         Point pointTo = new Point();          pointTo.x = toViewBounds.left;         pointTo.y = toViewBounds.top + (toViewBounds.bottom - toViewBounds.top) / 2;          return pointTo;     }       private void drawArrowLines(Point pointFrom, Point pointTo, Canvas canvas) {         canvas.drawLine(pointFrom.x, pointFrom.y, pointTo.x, pointTo.y, mPaint);          double angle = Math.atan2(pointTo.y - pointFrom.y, pointTo.x - pointFrom.x);          int arrowX, arrowY;          arrowX = (int) (pointTo.x - ARROW_SIZE * Math.cos(angle + ARROW_ANGLE));         arrowY = (int) (pointTo.y - ARROW_SIZE * Math.sin(angle + ARROW_ANGLE));         canvas.drawLine(pointTo.x, pointTo.y, arrowX, arrowY, mPaint);          arrowX = (int) (pointTo.x - ARROW_SIZE * Math.cos(angle - ARROW_ANGLE));         arrowY = (int) (pointTo.y - ARROW_SIZE * Math.sin(angle - ARROW_ANGLE));         canvas.drawLine(pointTo.x, pointTo.y, arrowX, arrowY, mPaint);     }      public void animateArrows(int duration) {         mDrawArrow = true;          View fromView = getChildAt(0);         View toView = getChildAt(1);          // find from and to views bounds         Rect fromViewBounds = new Rect();         fromView.getDrawingRect(fromViewBounds);         offsetDescendantRectToMyCoords(fromView, fromViewBounds);          Rect toViewBounds = new Rect();         toView.getDrawingRect(toViewBounds);         offsetDescendantRectToMyCoords(toView, toViewBounds);          // calculate arrow sbegin and end points         Point pointFrom = calcPointFrom(fromViewBounds, toViewBounds);         Point pointTo = calcPointTo(fromViewBounds, toViewBounds);          ValueAnimator arrowAnimator = createArrowAnimator(pointFrom, pointTo, duration);         arrowAnimator.start();     }      private ValueAnimator createArrowAnimator(Point pointFrom, Point pointTo, int duration) {          final double angle = Math.atan2(pointTo.y - pointFrom.y, pointTo.x - pointFrom.x);          mPointFrom.x = pointFrom.x;         mPointFrom.y = pointFrom.y;          int firstX = (int) (pointFrom.x + ARROW_SIZE * Math.cos(angle));         int firstY = (int) (pointFrom.y + ARROW_SIZE * Math.sin(angle));          PropertyValuesHolder propertyX = PropertyValuesHolder.ofInt(PROPERTY_X, firstX, pointTo.x);         PropertyValuesHolder propertyY = PropertyValuesHolder.ofInt(PROPERTY_Y, firstY, pointTo.y);          ValueAnimator animator = new ValueAnimator();         animator.setValues(propertyX, propertyY);         animator.setDuration(duration);         // set other interpolator (if needed) here:         animator.setInterpolator(new AccelerateDecelerateInterpolator());          animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {             @Override             public void onAnimationUpdate(ValueAnimator valueAnimator) {                 mPointTo.x = (int) valueAnimator.getAnimatedValue(PROPERTY_X);                 mPointTo.y = (int) valueAnimator.getAnimatedValue(PROPERTY_Y);                  invalidate();             }         });          return animator;     } } 

with .xml layout like:

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"                 android:id="@+id/layout_main"                 android:layout_width="match_parent"                 android:layout_height="wrap_content">      <{YOUR_PACKAGE_NAME}.ArrowLayout             android:id="@+id/arrow_layout"             android:layout_width="match_parent"             android:layout_height="match_parent">          <Button             android:id="@+id/first_button"             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:layout_alignParentLeft="true"             android:text="First Button"/>          <Button             android:id="@+id/second_button"             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:layout_alignParentRight="true"             android:text="Second Button"/>      </{YOUR_PACKAGE_NAME}.ArrowLayout>  </RelativeLayout> 

and MainActivity.java like:

public class MainActivity extends AppCompatActivity {      private ArrowLayout mArrowLayout;     private Button mFirstButton;      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);          mArrowLayout = (ArrowLayout) findViewById(R.id.arrow_layout);          mFirstButton = (Button) findViewById(R.id.first_button);         mFirstButton.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 mArrowLayout.animateArrows(1000);             }         });     } } 

you got something like that (on First Button click):

[Arrow to View animated

For other cases ( Second Button is exactly to the left (or above, or below) or more complex above-right/below-left etc. of First Button) you should modify part for calculating arrow begin and end points:

private Point calcPointFrom(Rect fromViewBounds, Rect toViewBounds) {     Point pointFrom = new Point();      //                                Second Button above     //                                ----------+----------     //                               |                     |     //  Second Button tho the left   +     First Button    + Second Button tho the right     //                               |                     |     //                                ----------+----------     //                                  Second Button below     //     //   + - is arrow start point position      if (toViewBounds to the right of fromViewBounds){         pointFrom.x = fromViewBounds.right;         pointFrom.y = fromViewBounds.top + (fromViewBounds.bottom - fromViewBounds.top) / 2;     } else if (toViewBounds to the left of fromViewBounds) {         pointFrom.x = fromViewBounds.left;         pointFrom.y = fromViewBounds.top + (fromViewBounds.bottom - fromViewBounds.top) / 2;     } else if () {         ...     }      return pointFrom; } 
Read More

Monday, July 2, 2018

Apply animation sequentially to multiple views

Leave a Comment

I have an activity with 3 views (buttonViews) in a vertical linear layout. I am generating (inflating) these views dynamically. I want to apply an animation such that, on activity start, the first buttons slide in -> 100 ms delay -> second button slide in -> 100 ms delay -> Third button slide in.

Attempt

I tried implementing it in this way:

private void setMainButtons() {     ArrayList<String> dashboardTitles = DashboardUtils.getDashboardTitles();     ArrayList<Integer> dashboardIcons = DashboardUtils.getDashboardIcons();      final ViewGroup root = findViewById(R.id.button_container);      for (int i = 0; i < (dashboardTitles.size() < dashboardIcons.size() ? dashboardTitles.size() : dashboardIcons.size()); i++){         final View buttonView = DashboardButtonInflater.getDashboardButton(root, dashboardTitles.get(i), dashboardIcons.get(i), this);         if (buttonView == null) continue;         buttonView.setOnClickListener(this);         root.addView(buttonView);         animateBottomToTop(buttonView, (long) (i*50)); // Calling method to animate buttonView     } }  //The function that adds animation to buttonView, with a delay. private void animateBottomToTop(final View buttonView,long delay) {     AnimationSet animationSet = new AnimationSet(false);     animationSet.addAnimation(bottomToTop);     animationSet.addAnimation(fadeIn);     animationSet.setStartOffset(delay);     buttonView.setAnimation(animationSet); } 

Result:

The above method waits for the total delay of all the views and at the end, aminates all the views together. I can guess the culprit here is the thread. The dealy is actually stopping the UI thread from doing any animation. I could be wrong though.

I also tried running the animation code inside

new Thread(new Runnable(){...}).run() 

but that didn't work either.

Expectations:

Can somebody help me achieve the one-by-one animation on buttonView? Thank you.

4 Answers

Answers 1

Animations are statefull objects, you should not use the same instance multiple times simultaneously. In your case the bottomToTop and fadeIn animations are shared between the animation sets. When the set starts (initialize() is called) it will set the start offset of its children.

For example the method could look like :

//The function that adds animation to buttonView, with a delay. private void animateBottomToTop(final View buttonView,long delay) {     AnimationSet animationSet = new AnimationSet(false);     // create new instances of the animations each time     animationSet.addAnimation(createBottomToTop());     animationSet.addAnimation(createFadeIn());     animationSet.setStartOffset(delay);     buttonView.setAnimation(animationSet); } 

Answers 2

The problem might be easily solved with Transitions API. Having declared a root layout with this xml:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"   android:id="@+id/content_frame"   android:layout_width="match_parent"   android:layout_height="match_parent"   android:orientation="vertical"/> 

Then inside activity:

class MainActivity : AppCompatActivity() {      lateinit var content: LinearLayout     private var counter = 0      override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         setContentView(R.layout.activity_main)          content = findViewById(R.id.content_frame)         // wait this view to be laid out and only then start adding and animating views         content.post { addNextChild() }     }      private fun addNextChild() {         // terminal condition         if (counter >= 3) return         ++counter          val button = createButton()         val slide = Slide()         slide.duration = 500         slide.startDelay = 100         slide.addListener(object : TransitionListenerAdapter() {             override fun onTransitionEnd(transition: Transition) {                 addNextChild()             }         })         TransitionManager.beginDelayedTransition(content, slide)         content.addView(button)     }      private fun createButton(): Button {         val button = Button(this)         button.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)         button.text = "button"         return button     }  } 

This chunk of code will result in following output:

You can adjust animation and delay times respectively.


If you want following behavior:

Then you can use following code:

class MainActivity : AppCompatActivity() {      lateinit var content: LinearLayout      override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         setContentView(R.layout.activity_main)          content = findViewById(R.id.content_frame)         content.post { addChildren() }     }      private fun addChildren() {         val button1 = createButton()         val button2 = createButton()         val button3 = createButton()          val slide1 = Slide()         slide1.duration = 500         slide1.addTarget(button1)          val slide2 = Slide()         slide2.duration = 500         slide2.startDelay = 150         slide2.addTarget(button2)          val slide3 = Slide()         slide3.duration = 500         slide3.startDelay = 300         slide3.addTarget(button3)          val set = TransitionSet()         set.addTransition(slide1)         set.addTransition(slide2)         set.addTransition(slide3)          TransitionManager.beginDelayedTransition(content, set)         content.addView(button1)         content.addView(button2)         content.addView(button3)     }      private fun createButton(): Button {         val button = Button(this)         button.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)         button.text = "button"         return button     } } 

Answers 3

Create method, which will accept Any number of Animation to invoke one after another. Just as example.

private void playOneAfterAnother(@NonNull Queue<Animation> anims) {      final Animation next = anims.poll();       /* You can set any other paramters,       like delay, for each next   Playing view, if any of course */       next.addListener(new AnimationListener() {             @Override             public void onAnimationEnd(Animator a) {                 if (!anim.isEmpty()) {                     playOneAfterAnother(anims);                 }             }             @Override             public void onAnimationStart(Animator a) {             }             @Override             public void onAnimationCancel(Animator a) {             }             @Override             public void onAnimationRepeat(Animator a) {             }         });      next.play(); } 

Or with delay for animations, it's easy too.

private void playOneAfterAnother(@NonNull Queue<Animation> anims,                    long offsetBetween, int nextIndex) {      final Animation next = anims.poll();       /* You can set any other paramters,       like delay, for each next   Playing view, if any of course */       next.setStartOffset(offsetBetween * nextIndex);      next.play();       if (!anim.isEmpty()) {          playOneAfterAnother(anims,                offsetBetween, nextIndex +1);      }  } 

Answers 4

Probably, what you need to use is AnimatorSet instead of AnimationSet. The AnimatorSet API allows you to choreograph animations in two ways: 1. PlaySequentially 2. PlayTogether using the apis:

AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playSequentially(anim1, anim2, anim3, ...); animatorSet.playTogether(anim1, anim2, anim3, ...); 

You can further add delays to your animation using

animatorSet.setStartDelay(); 

Visit the complete API docs here https://developer.android.com/reference/android/animation/AnimatorSet

Hope this helps!

Read More

Saturday, June 23, 2018

Android : How to clip views by parent, like CSS overflow:hidden

Leave a Comment

I have views as follows :

<LinearLayout     android:layout_width="match_parent"     android:layout_height="wrap_content"     android:layout_marginTop="@dimen/global_legal_gap"     android:clipToPadding="true"     android:clipChildren="true"     android:baselineAligned="false"     android:background="@drawable/post_sound_bg"     android:foreground="?attr/selectableItemBackgroundBorderless"     android:orientation="horizontal">      <RelativeLayout         android:layout_width="@dimen/post_sound_card_height"         android:layout_height="@dimen/post_sound_card_height">          <com.facebook.drawee.view.SimpleDraweeView             android:id="@+id/album_art"             android:layout_width="@dimen/post_sound_card_height"             android:layout_height="@dimen/post_sound_card_height"             fresco:backgroundImage="@drawable/music_placeholder" />          <RelativeLayout             android:id="@+id/play_icon_control"             android:layout_width="match_parent"             android:layout_height="match_parent"             android:layout_alignParentBottom="true"             android:visibility="gone">              <View                 android:layout_width="match_parent"                 android:layout_height="match_parent"                 android:layout_centerInParent="true"                 android:layout_margin="3dp" />         </RelativeLayout>     </RelativeLayout> <LinearLayout> 

As shown in the parent RelativeLayout, I'm using android:clipToPadding="true" and android:clipChildren="true", yet the children of this parent view are still protruding outside it.

Or I'm I doing this right? How do I achieve something like CSS's overflow:hidden?

2 Answers

Answers 1

Consider changing layouts. What you want can be done with ConstraintLayout.

Just set the the dimensions of the layout and don't set the constraint on the part you want to overflow/hide.

The following code shows a View that adjusts it dimensions to its constraint and another that overflows.

enter image description here

Create a new android project and paste this as activity_main.xml

<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout     xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     xmlns:tools="http://schemas.android.com/tools"     android:layout_width="match_parent"     android:layout_height="match_parent"     tools:context=".MainActivity"     android:layout_margin="55dp">       <ImageView         android:id="@+id/imageView3"         android:layout_width="0dp"         android:layout_height="0dp"         app:layout_constraintBottom_toTopOf="@+id/guideline"         app:layout_constraintEnd_toEndOf="parent"         app:layout_constraintStart_toStartOf="parent"         app:layout_constraintTop_toTopOf="parent"         app:srcCompat="@android:drawable/sym_def_app_icon"/>      <android.support.constraint.Guideline         android:id="@+id/guideline"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:orientation="horizontal"         app:layout_constraintGuide_begin="84dp"/>      <ImageView         android:id="@+id/imageView4"         android:layout_width="0dp"         android:layout_height="300dp"         app:layout_constraintEnd_toEndOf="parent"         app:layout_constraintStart_toStartOf="parent"         app:layout_constraintTop_toTopOf="@+id/guideline"         app:srcCompat="@mipmap/ic_launcher"/> </android.support.constraint.ConstraintLayout> 

Answers 2

Your parent view has

android:layout_width="match_parent" android:layout_height="wrap_content" 

which means that the view takes all available width and up to all available height if child views are large enough. With this setup you can't seethe overflow:hidden behaviour because the parent will resize itself to contain children up to the whole screen size.

Actually, default view behaviour in android is similar to overflow:hidden.

What you need to do to see it is set fixed dimentions on the parent.

Just try to use something like:

<LinearLayout     android:layout_width="20dp"     android:layout_height="20dp" 

and you'll get the idea.

On a different note, you don't need to have a LinearLayout just to host a RelayiveLayout - use the RelativeLayout directly. Also, using android:orientation="horizontal" makes it behave similarly to flexbox direction row, not sure if that's something you want here.

Read More

Wednesday, October 4, 2017

CoordinatorLayout: View disappears with custom behavior

Leave a Comment

I am a newbie to CoordinatorLayout and this is a really strange behavior I am getting in CoordinatorLayout.I have an ImageView(or more specifically a subclass of ImageView called CircleImageView(it houses the Profile pic in the center here)) as one of the children of the CoordinatorLayout. I have anchored this CircleImageView to the AppbarLayout(which is another child of the CoordinatorLayout). Here is the entire layout I have:

So far so good. I am currently able to scroll the AppbarLayout and the NestedScrollView moves along with it. But, I thought of animating the Profile pic to move to right as we scroll up and decided to rely on custom CoordinatorLayour.Behavior. I ended up with a custom behavior that tries translation of the CircleImageView. It's not complete yet but is supposed to translate the view roughly some amount except now, the CircleImageView has disappeared completely with the introduction of custom behavior.

What might be the reason for this?

Note: I have tried replacing the CircleImageView with an ImageView and the behavior remains the same.


Here is the layout for reference:

<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     xmlns:tools="http://schemas.android.com/tools"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:fitsSystemWindows="false">      <android.support.design.widget.AppBarLayout         android:id="@+id/main.appbar"         android:layout_width="match_parent"         android:layout_height="200dp"         android:fitsSystemWindows="false"         android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">          <android.support.design.widget.CollapsingToolbarLayout             android:id="@+id/collapsing_toolbar"             android:layout_width="match_parent"             android:layout_height="match_parent"             app:layout_scrollFlags="scroll|exitUntilCollapsed">              <android.support.v7.widget.Toolbar                 android:id="@+id/toolbar"                 android:layout_width="match_parent"                 android:layout_height="?attr/actionBarSize"                 android:background="?attr/colorPrimary"                 app:layout_collapseMode="pin"                 app:popupTheme="@style/ThemeOverlay.AppCompat.Light">              </android.support.v7.widget.Toolbar>              <android.support.v7.widget.AppCompatImageView                 android:id="@+id/imageView"                 android:layout_width="wrap_content"                 android:layout_height="wrap_content"                 android:layout_gravity="bottom|end"                 android:layout_marginBottom="24dp"                 android:layout_marginEnd="24dp"                 android:layout_marginRight="24dp"                 android:layout_weight="1"                 android:tint="@color/white"                 app:srcCompat="@drawable/ic_settings_24px" />          </android.support.design.widget.CollapsingToolbarLayout>       </android.support.design.widget.AppBarLayout>      <de.hdodenhof.circleimageview.CircleImageView         android:id="@+id/profile_pic"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:elevation="4dp"            app:layout_behavior="com.learncity.learner.account.profile.ProfilePicBehavior"         android:src="@drawable/avatar_boy_2"         app:layout_anchor="@id/main.appbar"         app:layout_anchorGravity="bottom|center" />      <android.support.v4.widget.NestedScrollView         android:layout_width="match_parent"         android:layout_height="match_parent"         android:fillViewport="true"         app:layout_behavior="@string/appbar_scrolling_view_behavior">          <android.support.constraint.ConstraintLayout             android:layout_width="match_parent"             android:layout_height="match_parent">               <TextView                 android:id="@+id/user_name"                 android:layout_width="0dp"                 android:layout_height="wrap_content"                 android:layout_marginEnd="8dp"                 android:layout_marginLeft="8dp"                 android:layout_marginRight="8dp"                 android:layout_marginStart="8dp"                 android:layout_marginTop="64dp"                 android:lineSpacingExtra="8dp"                 android:padding="@dimen/activity_horizontal_margin"                 android:text="@string/account_person_name_label"                 android:textAlignment="center"                 android:textSize="40sp"                 app:layout_constraintHorizontal_bias="0.0"                 app:layout_constraintLeft_toLeftOf="parent"                 app:layout_constraintRight_toRightOf="parent"                 app:layout_constraintTop_toTopOf="parent" />              <android.support.v7.widget.AppCompatImageView                 android:id="@+id/id_phone_icon"                 android:layout_width="wrap_content"                 android:layout_height="wrap_content"                 android:layout_marginLeft="24dp"                 android:layout_marginStart="24dp"                 android:layout_marginTop="8dp"                 android:tint="@color/colorPrimary"                 app:layout_constraintLeft_toLeftOf="parent"                 app:layout_constraintTop_toBottomOf="@+id/user_name"                 app:srcCompat="@drawable/ic_phone_black_24dp" />              <TextView                 android:id="@+id/id_phone_no"                 android:layout_width="wrap_content"                 android:layout_height="wrap_content"                 android:layout_marginLeft="16dp"                 android:layout_marginStart="16dp"                 android:text="Phone No"                 app:layout_constraintBottom_toBottomOf="@+id/id_phone_icon"                 app:layout_constraintLeft_toRightOf="@+id/id_phone_icon"                 app:layout_constraintTop_toTopOf="@+id/id_phone_icon"                 app:layout_constraintVertical_bias="0.571" />               <android.support.v7.widget.AppCompatImageView                 android:id="@+id/id_email_icon"                 android:layout_width="wrap_content"                 android:layout_height="wrap_content"                 android:layout_marginLeft="24dp"                 android:layout_marginStart="24dp"                 android:layout_marginTop="8dp"                 android:tint="@color/colorPrimary"                 app:layout_constraintLeft_toLeftOf="parent"                 app:layout_constraintTop_toBottomOf="@+id/id_phone_icon"                 app:srcCompat="@drawable/ic_email_black_24dp" />              <TextView                 android:id="@+id/id_email_id"                 android:layout_width="wrap_content"                 android:layout_height="wrap_content"                 android:layout_marginLeft="16dp"                 android:layout_marginStart="16dp"                 android:text="Email Id"                 app:layout_constraintBottom_toBottomOf="@+id/id_email_icon"                 app:layout_constraintLeft_toRightOf="@+id/id_email_icon"                 app:layout_constraintTop_toTopOf="@+id/id_email_icon" />              <View                 style="@style/Divider"                 android:layout_marginEnd="8dp"                 android:layout_marginLeft="8dp"                 android:layout_marginRight="8dp"                 android:layout_marginStart="8dp"                 android:layout_marginTop="8dp"                 app:layout_constraintTop_toBottomOf="@+id/id_email_icon" />          </android.support.constraint.ConstraintLayout>      </android.support.v4.widget.NestedScrollView>   </android.support.design.widget.CoordinatorLayout> 

And the custom behavior:(I know the calculations might not be sound but I am trying for an initial crude animation)

public class ProfilePicBehavior extends CoordinatorLayout.Behavior<CircleImageView>{       public ProfilePicBehavior(Context context, AttributeSet attrs) {         super(context, attrs);     }      @Override     public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View dependency) {         return dependency instanceof AppBarLayout;     }      @Override     public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) {         // Translate the CircleImageView to the right         // Calculate first, what fraction the AppBarLayout has shrunk by         float proportion = dependency.getHeight() / 200f;         // Translate the child by this proportion         float translationX = parent.getWidth() * proportion;         child.setTranslationX(translationX);          return true;     } } 

4 Answers

Answers 1

According to this answer: https://stackoverflow.com/a/40023161/6248491

It is stating that The contained CollapsingToolbarLayout fiddles with parent's elevation

Try giving elevation of the AppBarLayout to 0dp. Also, the CircleImageView should hold more (higher) elevation in order to not get "lifted" on top.

Hope this helps. Let me know if it works.

Answers 2

I got the same problem with you a few months ago. I solve it by a custom CoordinatorLayout.Behavior as below (I dont remember the link of same question):

public class CollapsingImageBehavior extends CoordinatorLayout.Behavior<View> {  private final static int X = 0; private final static int Y = 1; private final static int WIDTH = 2; private final static int HEIGHT = 3;  private int mTargetId;  private int[] mView;  private int[] mTarget;  public CollapsingImageBehavior() { }  public CollapsingImageBehavior(Context context, AttributeSet attrs) {      if (attrs != null) {         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CollapsingImageBehavior);         mTargetId = a.getResourceId(R.styleable.CollapsingImageBehavior_collapsedTarget, 0);         a.recycle();     }      if (mTargetId == 0) {         throw new IllegalStateException("collapsedTarget attribute not specified on view for behavior");     } }  @Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {     return dependency instanceof AppBarLayout; }  @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {      setup(parent, child);      AppBarLayout appBarLayout = (AppBarLayout) dependency;      int range = appBarLayout.getTotalScrollRange();     float factor = -appBarLayout.getY() / range;      int left = mView[X] + (int) (factor * (mTarget[X] - mView[X]));     int top = mView[Y] + (int) (factor * (mTarget[Y] - mView[Y]));     int width = mView[WIDTH] + (int) (factor * (mTarget[WIDTH] - mView[WIDTH]));     int height = mView[HEIGHT] + (int) (factor * (mTarget[HEIGHT] - mView[HEIGHT]));      CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();     lp.width = width;     lp.height = height;     child.setLayoutParams(lp);     child.setX(left);     child.setY(top);      return true; }  private void setup(CoordinatorLayout parent, View child) {      if (mView != null) return;      mView = new int[4];     mTarget = new int[4];      mView[X] = (int) child.getX();     mView[Y] = (int) child.getY();     mView[WIDTH] = child.getWidth();     mView[HEIGHT] = child.getHeight();      View target = parent.findViewById(mTargetId);     if (target == null) {         throw new IllegalStateException("target view not found");     }      mTarget[WIDTH] += target.getWidth();     mTarget[HEIGHT] += target.getHeight();      View view = target;     while (view != parent) {         mTarget[X] += (int) view.getX();         mTarget[Y] += (int) view.getY();         view = (View) view.getParent();     }  } } 

You have to add a custom styleable attrs.xml:

<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CollapsingImageBehavior">     <attr name="collapsedTarget" format="integer"></attr> </declare-styleable> </resources> 

After that, you can define your xml as below:

<?xml version="1.0" encoding="utf-8"?> 

<android.support.design.widget.AppBarLayout     android:id="@+id/main.appbar"     android:layout_width="match_parent"     android:layout_height="200dp"     android:fitsSystemWindows="false"     android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">      <android.support.design.widget.CollapsingToolbarLayout         android:id="@+id/collapsing_toolbar"         android:layout_width="match_parent"         android:layout_height="match_parent"         app:layout_scrollFlags="scroll|exitUntilCollapsed">          <android.support.v7.widget.Toolbar             android:id="@+id/toolbar"             android:layout_width="match_parent"             android:layout_height="?attr/actionBarSize"             android:background="?attr/colorPrimary"             app:layout_collapseMode="pin"             app:popupTheme="@style/ThemeOverlay.AppCompat.Light">              <Space                 android:id="@+id/circle_collapsed_target"                 android:layout_width="40dp"                 android:layout_height="40dp" />         </android.support.v7.widget.Toolbar>          <android.support.v7.widget.AppCompatImageView             android:id="@+id/imageView"             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:layout_gravity="bottom|end"             android:layout_marginBottom="24dp"             android:layout_marginEnd="24dp"             android:layout_marginRight="24dp"             android:layout_weight="1"             app:srcCompat="@drawable/ic_tab_angel" />       </android.support.design.widget.CollapsingToolbarLayout>   </android.support.design.widget.AppBarLayout>  <ImageView     android:id="@+id/profile_pic"     android:layout_width="100dp"     android:layout_height="100dp"     android:layout_gravity="top|center_horizontal"     android:layout_marginTop="220dp"     android:elevation="4dp"     android:src="@mipmap/icon_app"     app:collapsedTarget="@id/circle_collapsed_target"     app:layout_behavior="com.kp_corp.angelalarm.activity.CollapsingImageBehavior" /> <!---->  <android.support.v4.widget.NestedScrollView     android:layout_width="match_parent"     android:layout_height="match_parent"     android:fillViewport="true"     app:layout_behavior="@string/appbar_scrolling_view_behavior">      <android.support.constraint.ConstraintLayout         android:layout_width="match_parent"         android:layout_height="match_parent">          <TextView             android:id="@+id/user_name"             android:layout_width="0dp"             android:layout_height="wrap_content"             android:layout_marginEnd="8dp"             android:layout_marginLeft="8dp"             android:layout_marginRight="8dp"             android:layout_marginStart="8dp"             android:layout_marginTop="64dp"             android:lineSpacingExtra="8dp"             android:padding="@dimen/activity_horizontal_margin"             android:text="Test"             android:textAlignment="center"             android:textSize="40sp"             app:layout_constraintHorizontal_bias="0.0"             app:layout_constraintLeft_toLeftOf="parent"             app:layout_constraintRight_toRightOf="parent"             app:layout_constraintTop_toTopOf="parent" />       </android.support.constraint.ConstraintLayout>  </android.support.v4.widget.NestedScrollView> 

Answers 3

Last year I had to do something similar.

The AvatarImageBehavior is based on the GitHub project by Saul Molinero (saulmm)

As you can notice, the CircleImageView is the last element of the layout. Maybe it is your issue? Hope it helps.

fragment's layout

<android.support.design.widget.CoordinatorLayout     xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     xmlns:card_view="http://schemas.android.com/apk/res-auto"     xmlns:custom="http://schemas.android.com/apk/res-auto"     xmlns:tools="http://schemas.android.com/tools"     android:layout_width="match_parent"     android:layout_height="match_parent"     tools:ignore="RtlHardcoded">      <android.support.design.widget.AppBarLayout         android:id="@+id/main_appbar"         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">          <android.support.design.widget.CollapsingToolbarLayout             android:id="@+id/main.collapsing"             android:layout_width="match_parent"             android:layout_height="550dp"             app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">              <ImageView                 android:id="@+id/iv_product_background"                 android:layout_width="match_parent"                 android:layout_height="400dp"                 android:scaleType="centerCrop"                 android:tint="#11000000"                 app:layout_collapseMode="parallax"/>              <FrameLayout                 android:id="@+id/main_framelayout_title"                 android:layout_width="match_parent"                 android:layout_height="150dp"                 android:layout_gravity="bottom|center_horizontal"                 android:background="@color/white"                 android:orientation="vertical"                 app:layout_collapseMode="parallax">                  <LinearLayout                     android:id="@+id/main_linearlayout_title"                     android:layout_width="wrap_content"                     android:layout_height="wrap_content"                     android:layout_gravity="center"                     android:orientation="vertical">                      <TextView                         android:id="@+id/tv_product_title_open"                         android:layout_width="wrap_content"                         android:layout_height="wrap_content"                         android:layout_gravity="center_horizontal"                         android:gravity="bottom|center"                         tools:text="Title"                         android:textColor="@android:color/white"                         android:textSize="30sp"/>                      <TextView                         android:id="@+id/tv_product_tagline"                         android:layout_width="wrap_content"                         android:layout_height="wrap_content"                         android:layout_gravity="center_horizontal"                         android:layout_marginTop="4dp"                         android:gravity="center"                         android:paddingEnd="@dimen/standard_margin_space"                         android:paddingStart="@dimen/standard_margin_space"                         tools:text="Tagline"                         android:textColor="@android:color/white"/>                  </LinearLayout>             </FrameLayout>         </android.support.design.widget.CollapsingToolbarLayout>     </android.support.design.widget.AppBarLayout>       <android.support.v4.widget.NestedScrollView         android:layout_width="match_parent"         android:layout_height="match_parent"         android:background="@color/white"         android:scrollbars="none"         app:layout_behavior="@string/appbar_scrolling_view_behavior">          <LinearLayout             android:id="@+id/products_view_container"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:orientation="vertical"/>     </android.support.v4.widget.NestedScrollView>      <android.support.v7.widget.Toolbar         android:id="@+id/main_toolbar"         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:background="@color/white"         app:layout_anchor="@id/main_framelayout_title"         app:theme="@style/ThemeOverlay.AppCompat.Dark"         app:title="">          <include layout="@layout/toolbar_buttons"/>          <TextView             android:id="@+id/tv_product_title_closed"             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:layout_marginLeft="71dp"             android:gravity="center_vertical"             tools:text="Title"             android:textColor="@android:color/white"             android:textSize="26sp"/>          <!--</LinearLayout>-->     </android.support.v7.widget.Toolbar>      <de.hdodenhof.circleimageview.CircleImageView         android:id="@+id/iv_product_avatar"         android:layout_width="@dimen/product_avatar_width"         android:layout_height="@dimen/product_avatar_width"         android:layout_gravity="top|center_horizontal"         android:layout_marginTop="235dp"         android:src="@drawable/img_products_total16_avatar"         app:border_color="@color/grey"         app:border_width="0dp"         app:layout_behavior="com.myname.AvatarImageBehavior"/>  </android.support.design.widget.CoordinatorLayout> 

AvatarImageBehavior

public class AvatarImageBehavior extends CoordinatorLayout.Behavior<CircleImageView> {      private final static String TAG = AvatarImageBehavior.class.getSimpleName();     private final Context mContext;      private boolean isInitialized = false;      private float mStartX;     private float mMaxXMove;      private float mStartY;     private float mMaxYMove;      private float mMaxScroll;      private float mStartHeight;     private float mMaxHeightChange;      private float mFinalHeight;     private float mFinalX;     private float mFinalY;      public AvatarImageBehavior(Context context, AttributeSet attrs) {         mContext = context;     }      @Override     public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View dependency) {         return dependency instanceof Toolbar;     }      private void initProperties(CircleImageView child, View dependency) {         mMaxScroll = dependency.getY();          mStartHeight = child.getHeight();         mFinalHeight = mContext.getResources().getDimensionPixelOffset(R.dimen.product_avatar_final_width);         mMaxHeightChange = mStartHeight - mFinalHeight;          mStartX = child.getX();         mFinalX = mContext.getResources().getDimensionPixelOffset(R.dimen.product_avatar_margin_left);         mMaxXMove = mStartX - mFinalX;          mStartY = child.getY();         mFinalY = (dependency.getHeight() - mFinalHeight) / 2f;         mMaxYMove = mStartY - mFinalY;          isInitialized = true;     }      @Override     public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) {          if (!isInitialized)             initProperties(child, dependency);          final float currScrollDist = dependency.getY();          if (currScrollDist == 0) {             setParams(child, (int) mFinalX, (int) mFinalY, (int) mFinalHeight);         } else if (currScrollDist == mMaxScroll) { // reset the values if the scroll is at the max             setParams(child, (int) mStartX, (int) mStartY, (int) mStartHeight);         } else {             float scrollFactor = currScrollDist / mMaxScroll;             float factor = 1f - scrollFactor;              float currX = mStartX - (mMaxXMove * factor);             float currY = mStartY - (mMaxYMove * factor);             float currHeight = mStartHeight - (mMaxHeightChange * factor);              setParams(child, (int) currX, (int) currY, (int) currHeight);         }          return true;     }      private void setParams(CircleImageView view, int xPos, int yPos, int height) {         view.setX(xPos);         view.setY(yPos);         CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) view.getLayoutParams();         lp.width = height;         lp.height = height;         view.setLayoutParams(lp);     } } 

Answers 4

I have figured it out finally. I narrowed down the problem to the custom behavior(obviously) and the problem was the anomalous values of the translation for the child view(CircleImageView).

Here is the custom behavior after fine tuning the values:

public class ProfilePicBehavior extends CoordinatorLayout.Behavior<CircleImageView>{      private int mDependencyHeight;     private int mProfilePicMargin;     private int mActionBarHeight;      public ProfilePicBehavior(Context context) {         init(context);     }      public ProfilePicBehavior(Context context, AttributeSet attrs) {         super(context, attrs);         init(context);     }      private void init(Context context){          mDependencyHeight = (int)context.getResources()                 .getDimension(R.dimen.appbarlayout_learner_home_height);          mProfilePicMargin = (int)ViewUtils.dpToPx(context, 8f);          mActionBarHeight = (int)ActivityUtils.getActionBarHeight(context);     }      @Override     public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View dependency) {          if(dependency instanceof AppBarLayout){             return true;         }         return false;     }      @Override     public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) {         // Translate the CircleImageView to the right         // Calculate first, what fraction the AppBarLayout has shrunk by         int bottom = dependency.getBottom();         int top = dependency.getTop();         int viewHeight = bottom;          float proportion = Math.min(1, 1 - ((viewHeight - mActionBarHeight) / (float)(mDependencyHeight - mActionBarHeight)));         // Translate the child by this proportion         float translationX = (parent.getWidth()/2 - child.getWidth()/2 - mProfilePicMargin) * proportion;         float translationY = (child.getHeight()/2 - mProfilePicMargin) * proportion;          child.setTranslationX(translationX);         child.setTranslationY(translationY);          return true;     } } 

You can see that it is simpler than @Eselfar's answer which manipulates the properties through the LayoutParams.(I haven't tested his answer though because my animation was a bit different)

The logic is really simple: Translate the CircleImageView by the proportion that AppBarLayout has shrunk by.

Read More

Wednesday, August 9, 2017

Animate top and bottom dimensions of a view

Leave a Comment

I need to 2 two things with a view:

  1. Move top dimension to the very top of the window
  2. Move bottom dimension to the very bottom of the window.

In short, I need the view to cover the 100% of the parent view.

Translation animation didn't work because It moves the view but it doesn't increase the size

Scale animation works but it stretch the content of the view and I don't want that. I want to increase the visible area, not to stretch the content to fit the new dimensions.

What's the correct way to do this?

6 Answers

Answers 1

That can be easily achieved with Transitions API.

With Transitions API you do not take care of writing animations, you just tell what you want the end values be and Transitions API would take care of constructing animations.

Having this xml as content view (a view in the center of the screen):

<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:id="@+id/root"     android:layout_width="match_parent"     android:layout_height="match_parent">      <View         android:id="@+id/view"         android:layout_width="120dp"         android:layout_height="80dp"         android:layout_gravity="center"         android:background="@color/colorAccent" />  </FrameLayout> 

In activity:

override fun onCreate(savedInstanceState: Bundle?) {     super.onCreate(savedInstanceState)     setContentView(R.layout.item)      val root = findViewById(R.id.root) as ViewGroup     val view = findViewById(R.id.view)      view.setOnClickListener {          // After this line Transitions API would start counting the delta         // and will take care of creating animations for each view in `root`         TransitionManager.beginDelayedTransition(root)          // By default AutoTransition would be applied,         // but you can provide your transition with the second parameter          // val transition = AutoTransition()         // transition.duration = 2000         // TransitionManager.beginDelayedTransition(root, transition)          // We are changing size of the view to match parent         val params = view.layoutParams         params.height = ViewGroup.LayoutParams.MATCH_PARENT         params.width = ViewGroup.LayoutParams.MATCH_PARENT          view.requestLayout()     } } 

Here's the output:

Platform's Transitions API (android.transition.TransitionManager) is available from API 19, but support libraries backport the functionality upto API 14 (android.support.transition.TransitionManager).

Answers 2

You can try using ValueAnimator as shown in this answer: https://stackoverflow.com/a/32835417/3965050

Note: I wanted to write this as a comment, but I don't have the reputation. This should not be considered as a full answer.

Answers 3

I like to keep everything as simple as it can be.

so my suggestion would be using a android Animating Layout Changes

Here is a sample:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:animateLayoutChanges="true"     android:animationCache="true">      <TextView         android:id="@+id/textView"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:text="@string/app_name"         android:background="@color/colorPrimary"         android:layout_gravity="center" />  </LinearLayout> 

MainActivity.java

public class MainActivity extends AppCompatActivity {      TextView textView;      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);          textView = (TextView) findViewById(R.id.textView);     }      @Override     protected void onResume() {         super.onResume();          new Handler().postDelayed(new Runnable() {             @Override             public void run() {                  View view = getWindow().getDecorView();                  int height = getWindow().getDecorView().getHeight();                 int width = getWindow().getDecorView().getWidth();                 textView.setLayoutParams(new LinearLayout.LayoutParams(width, height));                  LayoutTransition layoutTransition = ((ViewGroup) textView.getParent()).getLayoutTransition();                 layoutTransition.enableTransitionType(LayoutTransition.CHANGING);             }         }, 2000);     } } 

Answers 4

animateLayoutChanges="true" in the parent xml 

+

.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 

does the trick most of the times and it won't stretch the existing child views

Answers 5

Using ConstraintLayout with ConstrainSet should match your need in the most efficient way.

public class MainActivity extends AppCompatActivity {     ConstraintSet mConstraintSet1 = new ConstraintSet(); // create a Constraint Set     ConstraintSet mConstraintSet2 = new ConstraintSet(); // create a Constraint Set     ConstraintLayout mConstraintLayout; // cache the ConstraintLayout     boolean mOld = true;      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         Context context = this;         mConstraintSet2.clone(context, R.layout.state2); // get constraints from layout         setContentView(R.layout.state1);         mConstraintLayout = (ConstraintLayout) findViewById(R.id.activity_main);         mConstraintSet1.clone(mConstraintLayout); // get constraints from ConstraintSet     }      public void foo(View view) {         TransitionManager.beginDelayedTransition(mConstraintLayout);         if (mOld = !mOld) {             mConstraintSet1.applyTo(mConstraintLayout); // set new constraints         }  else {             mConstraintSet2.applyTo(mConstraintLayout); // set new constraints         }     } } 

Source https://developer.android.com/reference/android/support/constraint/ConstraintSet.html

All you need is to define a second layout.xml with your expanded constraints and apply the second ConstraintSet to your view or activity when necessary.

Answers 6

ValueAnimator anim = ValueAnimator.ofInt(viewToIncreaseHeight.getMeasuredHeight(), -100); anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {     @Override     public void onAnimationUpdate(ValueAnimator valueAnimator) {         int val = (Integer) valueAnimator.getAnimatedValue();         ViewGroup.LayoutParams layoutParams = viewGroup.getLayoutParams();         layoutParams.height = val;         viewGroup.setLayoutParams(layoutParams);     } }); anim.setDuration(DURATION); anim.start();  
Read More

Wednesday, July 12, 2017

Topmost View swallows touch events because overlapping

Leave a Comment

So here's my layout structure:

- RelativeLayout (full screen)   - FrameLayout (full screen)   - Button (in the middle of the screen)   - RecyclerView (align parent bottom but with very big padding top to      capture the scrolling also in the top of the screen, so it's basically acting like a full screen `RecyclerView`) 

So yes, the views are overlapping each other.

As of the reason the RecyclerView is the topmost view, it captures all the touch events on screen, and swallows them, preventing any touches to "go through it" to the underlying views below it. (Note: by underlying views, I don't mean to the RecyclerView's children but the other rootview's children)

I've read tons of stackoverflow posts regarding propagating touch events and preventing swallowing touch events, etc, and even though it seems pretty straightforward task to accomplish, I couldn't achieve the following effect:

I want my RecyclerView to capture the touch event, so it will scroll or whatever. but I want the rootView to think as that the RecyclerView didn't capture the event, and continue pass it to its other children (rootview' s children).

Here's What I've tried to do:

1. Overriding dispatchTouchEvent of the RecyclerView to do its logic and return false to act as it didn't dispatch its touch event so the rootview will keep iterating for touch through its child views.

@Override public boolean dispatchTouchEvent(MotionEvent ev) {     super.dispatchTouchEvent(ev);     return false; } 

What happened:

The RecyclerView still functions but still swallows all touch events (just the same as before)

2. Overriding onTouchEvent of the RecyclerView to do its logic and return false. (Note: I know it doesn't seem as it would be the solution but I tried)

@Override public boolean onTouchEvent(MotionEvent e) {     super.onTouchEvent(e);     return false; } 

What happened:

Same result as in #1

I've made some more tweaks with the same idea, but they didn't work as well, so I'm kinda clueless right now and would love for help of you fellows!

3 Answers

Answers 1

I have two suggestions for you:
The first is simpler but assumes that you want to click on the Button and it can appear above the RecyclerView, if this is the case you can simply change the layout's order:

- RelativeLayout (full screen)   - FrameLayout (full screen)   - RecyclerView   - Button (in the middle of the screen) 

If you need something more general then it can be done like this:

recyclerView.setOnTouchListener(new View.OnTouchListener() {             @Override             public boolean onTouch(View v, MotionEvent event) {                 int [] pos = new int[2];                 recyclerView.getLocationOnScreen(pos);                 Rect rect = new Rect();                 button.getHitRect(rect);                 if (rect.contains((int) event.getX() + pos[0], (int) event.getY() + pos[1])) {                     button.onTouchEvent(event);                 }                 return false;             }         }); 

You will need to handle all your views separately, which is not as nice, but it works in my test.

Note: if you have added addOnItemTouchListenerto your RecyclerView you will need to add the said functionality to onInterceptTouchEvent (or to both places, depends on your items and RecyclerView).

Answers 2

I'm not sure how efficient/valid/nice solution is this, but this is what comes to my mind: what if you listen for touch events in the root view (RelativeLayout in your case), and dispatch the touch event to all the children of that layout except for the RecyclerView?

public class MyRelativeLayout extends RelativeLayout {     ...      @Override     public boolean onInterceptTouchEvent(MotionEvent ev) {         final boolean intercept = super.onInterceptTouchEvent(ev);          // getChildCount() - 1, because `RecyclerView` is the last child         for (int i = 0, size = getChildCount() - 1; i < size; i++) {             View v = getChildAt(i);             v.dispatchTouchEvent(MotionEvent.obtain(ev));         }          return intercept;     } } 

This seems like it should work.

Answers 3

I don't quite understand your problem, however if the goal is to not allow RecyclerView to consume any and all touch events, then simply set android:elevation="1dp"for the FrameLayout and Override dispatchTouchEvent() like so:

@Override public boolean dispatchTouchEvent(MotionEvent event) {     Logger.log("CustomFrameLayout : dispatchTouchEvent");     boolean result = super.dispatchTouchEvent(event);      ((View) getParent()).findViewById(R.id.recyclerView).dispatchTouchEvent(event);      return result;                 // This part can be changed according to requirement. } 

This may or may not have some undesired side effects, since I just came up with it on my own, however as much as I have checked it does not.

Cases I've tested:

  1. Scrolling in RecyclerView : Works.
  2. Clicks in RecyclerView Items : Works.
  3. Clicks in Children of FrameLayout : Works.

UPDATE :

Why is there any need for elevation for the FrameLayout?

android:elevation provides a means for position of z-axis of any layout. As to why there is a need to do so, to answer that I'll have to delve deeper and bore you with lengthy explanation (There is already loads of stuff written on it) suffice it to say that MotionEvents trickle down Views following some basic rules: From root to its children, The child to first get the event first (to check if the event belongs to it) trickles down on the basis of their position in the x-y axis and then on their order in z-axis (Top one gets the event first).


So In your case when a MotionEvent is detected it is passed to the RelativeLayout (RootView), which has 3 children, Button will be filtered because of the position. Now you have 2 Views a FrameLayout and RecyclerView. A RelativeLayout's bottom-most child comes on top of the z-axis, which means RecyclerView event will be dispatched to it first (which will naturally consume the event). If however a case arose in which the event was not consumed (your first attempt), the event will be passed on to the next child View until there are no more child Views left at which point the parent (RelativeLayout) will assume the event belongs to itself and call its own onInterceptTouchEvent(). During this process if a View has consumed the event all the subsequent calls will be passed to it directly (The reason you can only make one of the two either FrameLayout or RecyclerView work).

All of the above explanation means by the rulebook only one View will ever consume the event.

What I Did

I simply elevated the FrameLayout so it would be the official receiver of the event. Overrode the dispatchTouchEvent() and sent the same event to its neighboring RecyclerView too, which thinks the event has been brought to it by regular mode.

Now after all that explanation

The comment // This part can be changed according to requirement.

A better and possibly error-less method would be

boolean result2 = ((View) getParent()).findViewById(R.id.recyclerView).dispatchTouchEvent(event);  return result || result2; 

So if any of the Views have consumed the event the root will think that it has been consumed too.

Read More