Saturday, 30 April 2016

Android - Move/Drag views inside RelativeLayout

Ever wondered how can we move or drag a view inside a view group? Well it's very simple actually. Let's consider we have an Activity with RelativeLayout as root view group and inside that we have a View like this :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/rlroot"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <View

        android:id="@+id/v_red"
        android:layout_height="@dimen/square_size"
        android:layout_width="@dimen/square_size"
        android:background="@color/red"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"/>
</RelativeLayout>
You can have anything in place of view like Button or TextView. Create an object of RelativeLayout and View both in onCreate of your activity :

View view_r;
int xDelta,yDelta;
RelativeLayout rootLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    rootLayout = (RelativeLayout) findViewById(R.id.rlroot);

    view_r = findViewById(R.id.v_red);
    view_r.setOnTouchListener(this);
}

Since we set activity as the onTouchListener for view, implement View.onTouchListener on your activity. Then implement onTouch method like this :

@Overridepublic boolean onTouch(View v, MotionEvent event) {

    final int X = (int) event.getRawX(); //X coordinate of where you actually touched on screen
    final int Y = (int) event.getRawY(); //Y coordinate of where you actually touched on screen

    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams) v.getLayoutParams();

            Log.d("MainActivity","leftPos:"+v.getLeft()+"topPos:"+v.getTop());
            //this portion calculates where or how far inside have we touched the view from it's top left corner.           
            xDelta = X - lParams.leftMargin;
            yDelta = Y - lParams.topMargin;        Log.d("MainActivity","Action_Down:X="+X+",Y="+Y+",xD="+xDelta+",yD="+yDelta+",lm="+lParams.leftMargin+",tm="+lParams.topMargin);
            break;

            case MotionEvent.ACTION_UP:
            Log.d("MainActivity","Action_up");
            break;

            case MotionEvent.ACTION_POINTER_DOWN:
            Log.d("MainActivity","Action_Pointer_Down");
            break;

            case MotionEvent.ACTION_POINTER_UP:
            Log.d("MainActivity","Action_Pointer_Up");
            break;

            case MotionEvent.ACTION_MOVE:
            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) v.getLayoutParams();

            layoutParams.leftMargin = X - xDelta;
            layoutParams.topMargin = Y - yDelta;

            layoutParams.rightMargin = 0;
            layoutParams.bottomMargin = 0;
         Log.d("MainActivity","mw="+rootLayout.getMeasuredWidth()+",mh="+rootLayout.getMeasuredHeight());           
            v.setLayoutParams(layoutParams); 

           //v.animate().x(X-xDelta).y(Y-yDelta).setDuration(0).start();
            Log.d("MainActivity","Action_Move:X="+X+",Y="+Y+",xD="+xDelta+",yD="+yDelta);
            break;
     }
    rootLayout.invalidate(); //to ask android to redraw the view
    return true;
 }

This diagram will help you understand what are X, Y, XDelta and YDelta.


From above figure clearly X - XDelta gives the left margin that our view should have. Similarly Top Margin is equal to Y - YDelta.

 The result is something like this :


But we are not going to stop here. As usual we will checkout a more complicated scenario.
What if I have 4 views inside RelativeLayout and each view takes a spot in each corner of RelativeLayout like this :


Here is the xml layout :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/rlroot"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    >

    <View
        android:id="@+id/v_red"
        android:layout_height="@dimen/square_size"
        android:layout_width="@dimen/square_size"
        android:background="@color/red"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"/>

    <View
        android:id="@+id/v_blue"
        android:layout_height="@dimen/square_size"
        android:layout_width="@dimen/square_size"
        android:background="@color/blue"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        />
       
    <View
        android:id="@+id/v_green"
        android:layout_height="@dimen/square_size"
        android:layout_width="@dimen/square_size"
        android:background="@color/green"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"/>
       
    <View
        android:id="@+id/v_other"
        android:layout_height="@dimen/square_size"
        android:layout_width="@dimen/square_size"
        android:background="@color/other"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"/>
       
</RelativeLayout>
While working on a similar scenario what I found out was that LayoutParams.leftMargin always returned zero so above code failed. And the reason was attributes of View alignParentBottom, alignParentLeft etc. If you remove these attributes from all views it would work fine. But practically we would never want all views to be overlapped. So I found another way.
On "ACTION_DOWN" I removed those attributes and calculated the view's initial margin and set them programtically. Note that if you only remove those attributes and not set initial margin then the view would just jump to left top corner the moment you touch it. Here is the code :

@Override
    public boolean onTouch(View v, MotionEvent event) {

        final int X = (int) event.getRawX();
        final int Y = (int) event.getRawY();
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams) v.getLayoutParams();
                if(lParams.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM)==RelativeLayout.TRUE)
                {
                    lParams.topMargin = v.getTop();
                    lParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM,0);
                    Log.d("MainActivity","added Rule bottom");
                }
                if(lParams.getRule(RelativeLayout.ALIGN_PARENT_TOP)==RelativeLayout.TRUE)
                {
                    lParams.bottomMargin = v.getBottom();
                    lParams.addRule(RelativeLayout.ALIGN_PARENT_TOP,0);
                    Log.d("MainActivity","added Rule top");
                }
                if(lParams.getRule(RelativeLayout.ALIGN_PARENT_LEFT)==RelativeLayout.TRUE)
                {
                    lParams.rightMargin = v.getRight();
                    lParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT,0);
                    Log.d("MainActivity","added Rule left");
                }
                if(lParams.getRule(RelativeLayout.ALIGN_PARENT_RIGHT)==RelativeLayout.TRUE)
                {
                    lParams.leftMargin = v.getLeft();//rootLayout.getMeasuredWidth()-v.getWidth();
                    lParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,0);
                    Log.d("MainActivity","added Rule right");
                }

                Log.d("MainActivity","leftPos:"+v.getLeft()+"topPos:"+v.getTop());
               
                xDelta = X - lParams.leftMargin;
                yDelta = Y - lParams.topMargin;
               
                Log.d("MainActivity","Action_Down:X="+X+",Y="+Y+",xD="+xDelta+",yD="+yDelta+",lm="+lParams.leftMargin+",tm="+lParams.topMargin);
                break;
            case MotionEvent.ACTION_UP:
                Log.d("MainActivity","Action_up");
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                Log.d("MainActivity","Action_Pointer_Down");
                break;
            case MotionEvent.ACTION_POINTER_UP:
                Log.d("MainActivity","Action_Pointer_Up");
                break;
            case MotionEvent.ACTION_MOVE:
                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) v.getLayoutParams();
                layoutParams.leftMargin = X - xDelta;
                layoutParams.topMargin = Y - yDelta;
                layoutParams.rightMargin = 0;
                layoutParams.bottomMargin = 0;
              
                v.setLayoutParams(layoutParams);
                //v.animate().x(X-xDelta).y(Y-yDelta).setDuration(0).start();
                Log.d("MainActivity","Action_Move:X="+X+",Y="+Y+",xD="+xDelta+",yD="+yDelta);
                break;
        }
        rootLayout.invalidate();
        return true;
    }

As you can see below we can move the views around...



Please note that we can use animate method also to move view (this is commented in above code)
//v.animate().x(X-xDelta).y(Y-yDelta).setDuration(0).start(); 

Cool right? Now we know how to drag views around.
Stay tuned for the next article. Till then keep rocking!

Android : A lesson on Regular Expressions by examples


Every one who has ever worked on Regular Expressions knows tricky it can be (if you don't understand it completely!). And as frustrated and angry as we might be, we can't ignore how powerful regular expressions are. Now let me put this straight first. I don't plan to pretend here that I am an expert on Regular Expressions. In this blog I am going to share with you what I have learned after spending countless hours in frustration. I am going explain Regular Expressions with practical examples that you may face at your work. So if you want to brush up your memory on RegEx characters\symbols you can check my blog on Regular Expressions (although that blog was for PowerShell, symbols have same meaning). Or there are some excellent articles on Regular Expressions that you can go through first like this.

Now that you have basics let's see some practical examples :

Let's say you have to find a name from a given string. Now we know names start with uppercase character. So our question boils down to this : Write a regular expression to find words that start with uppercase character followed by lowercase characters?

Solution: Now there are multiple ways to achieve this. I will show you 2 ways.
1-  [A-Z][a-z]+
2-  \p{Lu}\p{Ll}+

If you have gone through the 2nd link I shared above carefully which happens to be google's documentation on Pattern you can atleast recognize the strange symbols in second solution.
What first solution tells is find all strings with uppercase characters followed by lowercase characters. It's as simple as that. "+" tells preceding character or group may occur one or more times.
Now second solution is an advanced and cleaner way of doing same thing. \p let's you select characters based on the class name you provide inside curly braces following it. In above example Lu and Ll mean uppercase letter and lowercase letter respectively. Please note in android you have to use extra escape characters. So regex would be something like \\p{Lu}\\p{Ll}+ while compiling it using Pattern class. So our code would look like this :


String test = "this is some random text to test REGEX Asutosh Nayak" 
Pattern pattern_u = Pattern.compile("[A-Z][a-z]+");//\p{Lu}\p{Ll}+
Matcher matcher_t = pattern_u.matcher(test);
String res = "";
int c = 0;
while(matcher_t.find())
{
        res += "Match:"+matcher_t.group();
 }
    res += "\n";
}
textview_test.setText(res);

Also keep in mind Matcher.group() or Matcher.group(0) returns matches for all the groups it found. So if you have groups in your regex and you want to fetch match for only a certain group use Matcher.group(index) where "index" starts from 1. 
To understand how group() works let's see this example : 



String test = "this is some random text to test REGEX Asutosh Nayak"
Pattern pattern_u = Pattern.compile("[A-Z][a-z]+");//\p{Lu}\p{Ll}+
Matcher matcher_t = pattern_u.matcher(test);
String res = "";
int c = 0;
while(matcher_t.find())
{
        res += "Match:"+matcher_t.group();
 }
    res += "\n";
}
textview_test.setText(res);

 

It gives result like this :





As you can see “Group No.1” returned “REGEX” which was found by regex within first pair of parentheses and “Group No.2” returned “Asutosh” which was our second group. For those of who are wondering what happened to “Nayak” note that our regex was for a word with all uppercase characters followed by a word with first character uppercase only.
Neat right? Not so difficult. But this was one of the simplest regular expressions. Now let’s write some RegEx on numbers. Nothing is complete without numbers.

What if you had a string and you wanted to find numbers in it but not just any number. An amount in "Rupees" or "INR". So our regex should be capable of finding numbers preceded by Rs or INR.
Solution : (?i)(?:\s(?:RS|INR)\.?\s?)(\d+(\.\d{1,2})?)
Here is the sample code :


String test = "this is some random text to test REGEX for amount Rs. 911.10. Let's see 909.98."

Pattern pattern_m = 
Pattern.compile("(?i)(?:\\s(?:RS|INR)\\.?\\s?)(\\d+(\\\\d{1,2})?)");          

Matcher matcher_t = pattern_m.matcher(test);

            String res = "";

            int c = 0;

            while(matcher_t.find())

           {

              res += "Match No."+ c++ +"\n";

              for(int i=0; i<=matcher_t.groupCount();i++)

              {

                  res += "  Group No."+i+"\n";

                  res += "    Match:"+matcher_t.group(i)+"\n";

              }

              res += "\n";

           }
            tv_test.setText(res);  

Here is how result looks :




It’s perfectly fine to panic! :-D. I will explain everything.
  • (?i) - This  tells that the regex that follows is case insensitive. So this regex will treat “RS” and “Rs” the same way.  If later you want to add a group to your regex which is case sensitive just add a (?-i) before it.
  • (?:)- This is called non capturing group. What it means is it will search for the patter that’s inside the parenthesis to determine the overall match but it won’t include this pattern in any group. As seen in above image.
  • \.?- ‘?’ is called optional quantifier. It means the character or group preceding it can occur at most once(0 or 1). So here it tells that “Rs” can be followed by a “.”.
  • (\d+(\.\d{1,2})?)- This pattern is used to recognize any decimal number. \d+ means one or more number of digits. So digits followed by pattern for period followed by 1 or 2 (at most) digits which is optional since numbers may not have decimal portion.
That’s all there is to it. These were the confusing symbols in this regex.
Let’s make it even tougher. What if your string has now two numbers and you want to get the ordinary number not the money value. So we have to build a regular expression to find a number which is not preceded by Rs or INR. Sample String : "this is some random text to test REGEX for amount Rs. 911.10. Let's see 909.98 Test."

Solution: (?i)[^(Rs|INR\.?\s?)](\s\d+(\.\d{1,2})?\s?)

Tip: while writing complicated regular expressions always try to start small. Like if you have to find numbers not preceded by Rs or INR try first finding Rs or INR, then find numbers with Rs or INR and then finally negate the Rs or INR group. This will help you find which portion of regex is not working.

Using similar code as above and doing necessary changes to string and regular expression following result can be found :
   



All we did was surround the regex for finding "Rs or INR" with within square brackets and add an "^" to it. "^" is like logical NOT. So it signifies that we want numbers which are not preceded by Rs or INR.

Regular Expressions can be tricky to debug and really frustrating sometimes. But it’s a really powerful tool at our hands to quickly search for a pattern.