24 Apr 2014

Draggable Panel

Draggable Panel is an Android library created to build a draggable user interface similar to the new YouTube draggable video component based on Fragments or Views.
This Android library offers you two main classes to use and create your own awesome user interfaces. If you want to use it with fragments add DraggablePanel to your layout. If you want to use it with views useDraggableView and put your views inside.
This new component has been created using some concepts described on Flavien Laurent Blog and Denevell Blog.
To create this library I've used an Android component called ViewDragHelper and ViewDragHelper.Calback. This component doesn't have too much documentation and that's the reason why I've added some javadoc to my code in order to clarify the component usage.
This library works on Android 4.X because the scale effect is not going to work properly when the user try to drag the view. The clickable zone on a scaled view in Android 2.X is bigger than the real scaled zone.




Usage

To use Draggable Panel library and get your new awesome UI working you can use two different Android widgets:
  • 1. Add DraggablePanel widget to your layout. Configure the view customization elements using styleable attributes or programatically and configure your fragments to work as top or bottom fragment inside theDraggablePanel widget.
<com.github.pedrovgs.DraggablePanel
        android:id="@+id/draggable_panel"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"/>
private void initializeDraggablePanel() throws Resources.NotFoundException {
      draggablePanel.setFragmentManager(getSupportFragmentManager());
      draggablePanel.setTopFragment(placeFragment);
      draggablePanel.setBottomFragment(mapFragment);
      draggablePanel.initializeView();
}
  • 2. Add DraggableView widget to your layout. Configure the view to use two children views as the draggable view and the second view. Configure the customization elements using styleable attributes or programatically.
<com.github.pedrovgs.DraggableView
          xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:draggable_view="http://schemas.android.com/apk/res-auto"
          android:id="@+id/draggable_view"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          draggable_view:top_view_id="@+id/iv_fan_art"
          draggable_view:bottom_view_id="@+id/lv_episodes"
          draggable_view:top_view_x_scale_factor="@dimen/x_scale_factor"
          draggable_view:top_view_y_scale_factor="@dimen/y_scale_factor"
          draggable_view:top_view_margin_right="@dimen/top_fragment_margin"
          draggable_view:top_view_margin_bottom="@dimen/top_fragment_margin"
          android:background="@color/black">

      <!-- ListView Episodes -->

      <ListView
              android:id="@+id/lv_episodes"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:layout_below="@+id/iv_fan_art"
              style="@style/episodes_list_view"/>

      <!-- TvShow Fan Art -->

      <ImageView
              android:id="@+id/iv_fan_art"
              android:layout_width="fill_parent"
              android:layout_height="@dimen/tv_show_fan_art_height"
              android:layout_alignParentTop="true"
              style="@style/image_view"/>

</com.github.pedrovgs.DraggableView>

Import DraggablePanel dependency

Download the project, compile it using maven or gradle and import draggablepanel-1.0.2.aar into your project.
Or declare it into your pom.xml. This library uses NineOldAndroid library and Android support library v4 version 19.1.0, you have to provide this dependencies from your local artifact repository or from maven central repository.
<dependency>
  <groupId>com.github.pedrovgs</groupId>
  <artifactId>draggablepanel</artifactId>
  <version>1.0.2</version>
  <type>aar</type>
</dependency>
Or into your build.gradle
dependencies {
    compile 'com.github.pedrovgs:draggablepanel:1.0.2@aar'
    compile 'com.android.support:support-v4:19.1.+'
    compile 'com.nineoldandroids:library:2.4.+'
}

Customization

You can customize some of the view effects programatically or using xml styleable attributes. The elements to customize are:
  • Draggable view / fragment height.
  • Draggable view X scale factor.
  • Draggable view Y scale factor.
  • Draggable view margin right applied when the view is minimized.
  • Draggable view margin bottom applied when the view is minimized.
  • Enable or disable the horizontal alpha effect applied while the view is being horizontally dragged.
<com.github.pedrovgs.DraggableView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:draggable_view="http://schemas.android.com/apk/res-auto"
        android:id="@+id/draggable_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        draggable_view:top_view_id="@+id/iv_fan_art"
        draggable_view:bottom_view_id="@+id/lv_episodes"
        draggable_view:top_view_x_scale_factor="@dimen/x_scale_factor"
        draggable_view:top_view_y_scale_factor="@dimen/y_scale_factor"
        draggable_view:top_view_margin_right="@dimen/top_fragment_margin"
        draggable_view:top_view_margin_bottom="@dimen/top_fragment_margin"
        android:background="@color/black">

        <!-- ....... -->

</com.github.pedrovgs.DraggableView>
draggablePanel.setTopFragment(placeFragment);
draggablePanel.setBottomFragment(mapFragment);
draggablePanel.setXScaleFactor(xScaleFactor);
draggablePanel.setYScaleFactor(yScaleFactor);
draggablePanel.setTopViewHeight(topViewHeight);
draggablePanel.setTopFragmentMarginRight(topViewMarginRight);
draggablePanel.setTopFragmentMarginBottom(topViewMargnBottom);
Similar customizable attributes are available programatically or using styleable attributes in DraggableView.

Click here to download this project.

26 Mar 2014

Snippet: align a TextView around an image

In this post I will describe how to realize a particular layout not very common on Android: a text around an image.

This layout is not an Android Pattern, but it can be useful in same cases.
As always it is just an example, and you should improve some points in your real project.

Use a simple layout:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:text="Medium Text"
            android:textAppearance="?android:attr/textAppearanceMedium" />

        <ImageView
            android:id="@+id/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/airport1" />
    </RelativeLayout>


</ScrollView>

To achieve our scope, we can use a LeadingMarginSpan.LeadingMarginSpan2.
This span allows the implementor to specify the number of lines of text to which this object is attached that the "first line of paragraph" margin width will be applied to.

 /**
     *
     */
    class MyLeadingMarginSpan2 implements LeadingMarginSpan.LeadingMarginSpan2 {

        private int margin;
        private int lines;

        MyLeadingMarginSpan2(int lines, int margin) {
            this.margin = margin;
            this.lines = lines;
        }

        /**
         * Apply the margin
         *
         * @param first
         * @return
         */
        @Override
        public int getLeadingMargin(boolean first) {
            if (first) {
                return margin;
            } else {
                return 0;
            }
        }

        @Override
        public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
                                      int top, int baseline, int bottom, CharSequence text,
                                      int start, int end, boolean first, Layout layout) {}


        @Override
        public int getLeadingMarginLineCount() {
            return lines;
        }
    };

We only need to calculate the number of lines where we would like applying a margin and the right margin.
In this case we will get number of lines = height of image and margin = width of image + little extra margin. 

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = (TextView) findViewById(R.id.text);
        mImageView = (ImageView) findViewById(R.id.icon);


        final ViewTreeObserver vto = mImageView.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                finalHeight = mImageView.getMeasuredHeight();
                finalWidth = mImageView.getMeasuredWidth();
                makeSpan();
            }
        });
    }
}

This code can be improved.
I am using a very simple (and raw) float textLineHeight = mTextView.getPaint().getTextSize(); to calculate the number of lines.
You can add paddings, margins or you can use a Rect to calculate the text bounds.

  /**
     * This method builds the text layout
     */
    private void makeSpan() {

        /**
         * Get the text
         */
        String plainText=getResources().getString(R.string.text_sample);
        

        int allTextStart = 0;
        int allTextEnd = htmlText.length() - 1;

        /**
         * Calculate the lines number = image height.
         * You can improve it... it is just an example
         */
        int lines;
        Rect bounds = new Rect();
        mTextView.getPaint().getTextBounds(plainText.substring(0,10), 0, 1, bounds);

        //float textLineHeight = mTextView.getPaint().getTextSize();
        float fontSpacing=mTextView.getPaint().getFontSpacing();
        lines = (int) (finalHeight/fontSpacing);

        /**
         * Build the layout with LeadingMarginSpan2
         */
        MyLeadingMarginSpan2 span = new MyLeadingMarginSpan2(lines, finalWidth +10 );
        mSpannableString.setSpan(span, allTextStart, allTextEnd,
                   Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        mTextView.setText(mSpannableString);
    }



You can get code from GitHub:


19 Mar 2014

Android using Analog Clock and Digital Clock

An Android Analog and Digital Clock widgets are used to display Clock in Analog and Digital format. Since the DigitalClock package is deprecated we are going to use TextClock to display Digital Clock.


Creating Project:

Make sure you have properly setup the Android SDK, AVD for Testing the Application. Create a New project in Eclipse IDE with the package as “com.learn2crack.clock“. Create the Main Activity as “MainActivity” and the main Layout as “activity_main“.

Creating Layout:

The Main layout for our project is “activity_main” which has a Button widget to switch between AnalogClock and TextClock widgets.

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >
     <TextView
        android:id="@+id/welcome"
        android:layout_gravity="center"
        android:textSize="20sp"
        android:text="Android Clock"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
     <Button
        android:id="@+id/btn"
        android:layout_gravity="center"
        android:textSize="20sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextClock
        android:id="@+id/clock"
        android:layout_gravity="center"
        android:textSize="40sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <AnalogClock
        android:id="@+id/aclock"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
</LinearLayout>


Creating Activity:

First we are importing the TextClock and AnalogClock. When the button is pressed it hides one widget and enables other widget.

MainActivity.java


import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextClock;
import android.widget.AnalogClock;
import android.app.Activity;
public class MainActivity extends Activity {
  TextClock clock;
  AnalogClock aclock;
  Button btn;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    clock = (TextClock)findViewById(R.id.clock);
    aclock = (AnalogClock)findViewById(R.id.aclock);
    btn = (Button)findViewById(R.id.btn);
    btn.setText("Show Analog Clock");
    aclock.setVisibility(View.INVISIBLE);
      btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View arg0) {
      // TODO Auto-generated method stub
      btn.setText("Show Digital Clock");
      aclock.setVisibility(View.VISIBLE);
      clock.setVisibility(View.INVISIBLE);
      btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View arg0) {
          // TODO Auto-generated method stub
        onCreate(null);
        }
      });
    }
  });
  }
}




Creating Manifest:

No other special Permissions are required for our project.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
    package="com.learn2crack.clock"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="17"
        android:targetSdkVersion="18" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.learn2crack.clock.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>



Screenshots:











Finally run the project in the Emulator.
Enjoy :)
Any questions comment here.