Compatible Android

This commit is contained in:
Andros Fenollosa
2016-11-03 00:05:36 +01:00
parent 7cb6af1390
commit 8ec8327e5e
1793 changed files with 440698 additions and 7 deletions

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<globals>
<global id="manifestOut" value="${manifestDir}" />
<global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
<!-- e.g. getSupportActionBar vs. getActionBar -->
<global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="resOut" value="${resDir}" />
<global id="menuName" value="${classToResource(activityClass)}" />
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0"?>
<recipe>
<#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<instantiate from="res/menu/main.xml.ftl"
to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
<merge from="res/values/strings.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
<merge from="res/values/dimens.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
<merge from="res/values-w820dp/dimens.xml"
to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
<instantiate from="res/layout/activity_simple.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
<instantiate from="src/app_package/SimpleActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
</recipe>

View File

@@ -0,0 +1,24 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
<activity android:name="${relativePackage}.${activityClass}"
<#if isNewProject>
android:label="@string/app_name"
<#else>
android:label="@string/title_${activityToLayout(activityClass)}"
</#if>
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
<#if parentActivityClass != "">
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="${parentActivityClass}" />
</#if>
<#if isLauncher>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</#if>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,16 @@
<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="${relativePackage}.${activityClass}">
<TextView
android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>

View File

@@ -0,0 +1,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
xmlns:app="http://schemas.android.com/apk/res-auto"</#if>
xmlns:tools="http://schemas.android.com/tools"
tools:context="${relativePackage}.${activityClass}" >
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
${(appCompat)?string('app','android')}:showAsAction="never" />
</menu>

View File

@@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,9 @@
<resources>
<#if !isNewProject>
<string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
</#if>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
</resources>

View File

@@ -0,0 +1,35 @@
package ${packageName};
import <#if appCompat>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
public class ${activityClass} extends ${(appCompat)?string('ActionBar','')}Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.${layoutName});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.${menuName}, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -0,0 +1,71 @@
<?xml version="1.0"?>
<template
format="3"
revision="4"
name="Blank Activity"
minApi="7"
minBuildApi="14"
description="Creates a new blank activity with an action bar.">
<category value="Activity" />
<formfactor value="Mobile" />
<parameter
id="activityClass"
name="Activity Name"
type="string"
constraints="class|unique|nonempty"
suggest="${layoutToActivity(layoutName)}"
default="MainActivity"
help="The name of the activity class to create" />
<parameter
id="layoutName"
name="Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="${activityToLayout(activityClass)}"
default="activity_main"
help="The name of the layout to create for the activity" />
<parameter
id="activityTitle"
name="Title"
type="string"
constraints="nonempty"
default="MainActivity"
suggest="${activityClass}"
help="The name of the activity. For launcher activities, the application title." />
<parameter
id="isLauncher"
name="Launcher Activity"
type="boolean"
default="false"
help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
<parameter
id="parentActivityClass"
name="Hierarchical Parent"
type="string"
constraints="activity|exists|empty"
default=""
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
<parameter
id="packageName"
name="Package name"
type="string"
constraints="package"
default="com.mycompany.myapp" />
<!-- 128x128 thumbnails relative to template.xml -->
<thumbs>
<!-- default thumbnail is required -->
<thumb>template_blank_activity.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<globals>
<global id="manifestOut" value="${manifestDir}" />
<global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
<!-- e.g. getSupportActionBar vs. getActionBar -->
<global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="resOut" value="${resDir}" />
<global id="menuName" value="${classToResource(activityClass)}" />
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0"?>
<recipe>
<#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<instantiate from="res/menu/main.xml.ftl"
to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
<merge from="res/values/strings.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
<merge from="res/values/dimens.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
<merge from="res/values-w820dp/dimens.xml"
to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
<instantiate from="res/layout/activity_fragment_container.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
<instantiate from="res/layout/fragment_simple.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
<instantiate from="src/app_package/SimpleActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
</recipe>

View File

@@ -0,0 +1,24 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
<activity android:name="${relativePackage}.${activityClass}"
<#if isNewProject>
android:label="@string/app_name"
<#else>
android:label="@string/title_${activityToLayout(activityClass)}"
</#if>
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
<#if parentActivityClass != "">
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="${parentActivityClass}" />
</#if>
<#if isLauncher>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</#if>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}"
tools:ignore="MergeRootFrame" />

View File

@@ -0,0 +1,16 @@
<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="${relativePackage}.${activityClass}$PlaceholderFragment">
<TextView
android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>

View File

@@ -0,0 +1,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
xmlns:app="http://schemas.android.com/apk/res-auto"</#if>
xmlns:tools="http://schemas.android.com/tools"
tools:context="${relativePackage}.${activityClass}" >
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
${(appCompat)?string('app','android')}:showAsAction="never" />
</menu>

View File

@@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,7 @@
<resources>
<#if !isNewProject>
<string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
</#if>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
</resources>

View File

@@ -0,0 +1,32 @@
package ${packageName};
import <#if appCompat>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
import android.<#if appCompat>support.v7.</#if>app.ActionBar;
import android.<#if appCompat>support.v4.</#if>app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.os.Build;
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
public class ${activityClass} extends ${appCompat?string('ActionBar','')}Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.${layoutName});
if (savedInstanceState == null) {
get${Support}FragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment())
.commit();
}
}
<#include "include_options_menu.java.ftl">
<#include "include_fragment.java.ftl">
}

View File

@@ -0,0 +1,15 @@
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.${fragmentLayoutName}, container, false);
return rootView;
}
}

View File

@@ -0,0 +1,19 @@
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.${menuName}, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}

View File

@@ -0,0 +1,80 @@
<?xml version="1.0"?>
<template
format="3"
revision="4"
name="Blank Activity with Fragment"
minApi="7"
minBuildApi="14"
description="Creates a new blank activity, with an action bar and a contained Fragment.">
<category value="Activity" />
<formfactor value="Mobile" />
<parameter
id="activityClass"
name="Activity Name"
type="string"
constraints="class|unique|nonempty"
suggest="${layoutToActivity(layoutName)}"
default="MainActivity"
help="The name of the activity class to create" />
<parameter
id="layoutName"
name="Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="${activityToLayout(activityClass)}"
default="activity_main"
help="The name of the layout to create for the activity" />
<parameter
id="fragmentLayoutName"
name="Fragment Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="fragment_${classToResource(activityClass)}"
default="fragment_main"
help="The name of the layout to create for the activity's content fragment"/>
<parameter
id="activityTitle"
name="Title"
type="string"
constraints="nonempty"
default="MainActivity"
suggest="${activityClass}"
help="The name of the activity. For launcher activities, the application title." />
<parameter
id="isLauncher"
name="Launcher Activity"
type="boolean"
default="false"
help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
<parameter
id="parentActivityClass"
name="Hierarchical Parent"
type="string"
constraints="activity|exists|empty"
default=""
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
<parameter
id="packageName"
name="Package name"
type="string"
constraints="package"
default="com.mycompany.myapp" />
<!-- 128x128 thumbnails relative to template.xml -->
<thumbs>
<!-- default thumbnail is required -->
<thumb>template_blank_activity_fragment.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<globals>
<global id="manifestOut" value="${manifestDir}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="resOut" value="${resDir}" />
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<recipe>
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<merge from="res/values/strings.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
<copy from="res/layout/activity_simple.xml"
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
<instantiate from="src/app_package/SimpleActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
</recipe>

View File

@@ -0,0 +1,20 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
<activity android:name="${relativePackage}.${activityClass}"
<#if isNewProject>
android:label="@string/app_name"
<#else>
android:label="@string/title_${activityToLayout(activityClass)}"
</#if>
>
<#if isLauncher>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</#if>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,12 @@
<RelativeLayout 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"
tools:context="${relativePackage}.${activityClass}">
<TextView
android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>

View File

@@ -0,0 +1,6 @@
<resources>
<#if !isNewProject>
<string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
</#if>
<string name="hello_world">Hello world!</string>
</resources>

View File

@@ -0,0 +1,15 @@
package ${packageName};
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
public class ${activityClass} extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.${layoutName});
}
}

View File

@@ -0,0 +1,63 @@
<?xml version="1.0"?>
<template
format="3"
revision="4"
name="Empty Activity"
minApi="7"
minBuildApi="14"
description="Creates a new empty activity">
<category value="Activity" />
<formfactor value="Mobile" />
<parameter
id="activityClass"
name="Activity Name"
type="string"
constraints="class|unique|nonempty"
suggest="${layoutToActivity(layoutName)}"
default="MainActivity"
help="The name of the activity class to create" />
<parameter
id="layoutName"
name="Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="${activityToLayout(activityClass)}"
default="activity_main"
help="The name of the layout to create for the activity" />
<parameter
id="activityTitle"
name="Title"
type="string"
constraints="nonempty"
default="MainActivity"
suggest="${activityClass}"
help="The name of the activity. For launcher activities, the application title." />
<parameter
id="isLauncher"
name="Launcher Activity"
type="boolean"
default="false"
help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
<parameter
id="packageName"
name="Package name"
type="string"
constraints="package"
default="com.mycompany.myapp" />
<!-- 128x128 thumbnails relative to template.xml -->
<thumbs>
<!-- default thumbnail is required -->
<thumb>template_blank_activity.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<globals>
<global id="projectOut" value="." />
<global id="manifestOut" value="${manifestDir}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="resOut" value="${resDir}" />
<global id="simpleName" value="${activityToLayout(activityClass)}" />
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0"?>
<recipe>
<dependency mavenUrl="com.android.support:support-v4:19.+" />
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<merge from="res/values/attrs.xml"
to="${escapeXmlAttribute(resOut)}/values/attrs.xml" />
<merge from="res/values/colors.xml"
to="${escapeXmlAttribute(resOut)}/values/colors.xml" />
<merge from="res/values/styles.xml"
to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
<merge from="res/values-v11/styles.xml"
to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
<instantiate from="res/layout/activity_fullscreen.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
<merge from="res/values/strings.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
<instantiate from="src/app_package/FullscreenActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<instantiate from="src/app_package/util/SystemUiHider.java.ftl"
to="${escapeXmlAttribute(srcOut)}/util/SystemUiHider.java" />
<instantiate from="src/app_package/util/SystemUiHiderBase.java.ftl"
to="${escapeXmlAttribute(srcOut)}/util/SystemUiHiderBase.java" />
<instantiate from="src/app_package/util/SystemUiHiderHoneycomb.java.ftl"
to="${escapeXmlAttribute(srcOut)}/util/SystemUiHiderHoneycomb.java" />
<open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
</recipe>

View File

@@ -0,0 +1,26 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
<activity android:name="${relativePackage}.${activityClass}"
<#if isNewProject>
android:label="@string/app_name"
<#else>
android:label="@string/title_${simpleName}"
</#if>
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/FullscreenTheme"
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
<#if parentActivityClass != "">
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="${parentActivityClass}" />
</#if>
<#if isLauncher>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</#if>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,46 @@
<FrameLayout 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:background="#0099cc"
tools:context="${relativePackage}.${activityClass}">
<!-- The primary full-screen view. This can be replaced with whatever view
is needed to present your content, e.g. VideoView, SurfaceView,
TextureView, etc. -->
<TextView android:id="@+id/fullscreen_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"
android:textColor="#33b5e5"
android:textStyle="bold"
android:textSize="50sp"
android:gravity="center"
android:text="@string/dummy_content" />
<!-- This FrameLayout insets its children based on system windows using
android:fitsSystemWindows. -->
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<LinearLayout android:id="@+id/fullscreen_content_controls"
style="?metaButtonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:background="@color/black_overlay"
android:orientation="horizontal"
tools:ignore="UselessParent">
<Button android:id="@+id/dummy_button"
style="?metaButtonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/dummy_button" />
</LinearLayout>
</FrameLayout>
</FrameLayout>

View File

@@ -0,0 +1,15 @@
<resources>
<style name="FullscreenTheme" parent="android:Theme.Holo">
<item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
<item name="android:windowActionBarOverlay">true</item>
<item name="android:windowBackground">@null</item>
<item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
<item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
</style>
<style name="FullscreenActionBarStyle" parent="android:Widget.Holo.ActionBar">
<item name="android:background">@color/black_overlay</item>
</style>
</resources>

View File

@@ -0,0 +1,12 @@
<resources>
<!-- Declare custom theme attributes that allow changing which styles are
used for button bars depending on the API level.
?android:attr/buttonBarStyle is new as of API 11 so this is
necessary to support previous API levels. -->
<declare-styleable name="ButtonBarContainerTheme">
<attr name="metaButtonBarStyle" format="reference" />
<attr name="metaButtonBarButtonStyle" format="reference" />
</declare-styleable>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<color name="black_overlay">#66000000</color>
</resources>

View File

@@ -0,0 +1,9 @@
<resources>
<#if !isNewProject>
<string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
</#if>
<string name="dummy_button">Dummy Button</string>
<string name="dummy_content">DUMMY\nCONTENT</string>
</resources>

View File

@@ -0,0 +1,22 @@
<resources>
<style name="FullscreenTheme" parent="android:Theme.NoTitleBar">
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowBackground">@null</item>
<item name="metaButtonBarStyle">@style/ButtonBar</item>
<item name="metaButtonBarButtonStyle">@style/ButtonBarButton</item>
</style>
<!-- Backward-compatible version of ?android:attr/buttonBarStyle -->
<style name="ButtonBar">
<item name="android:paddingLeft">2dp</item>
<item name="android:paddingTop">5dp</item>
<item name="android:paddingRight">2dp</item>
<item name="android:paddingBottom">0dp</item>
<item name="android:background">@android:drawable/bottom_bar</item>
</style>
<!-- Backward-compatible version of ?android:attr/buttonBarButtonStyle -->
<style name="ButtonBarButton" />
</resources>

View File

@@ -0,0 +1,198 @@
package ${packageName};
import ${packageName}.util.SystemUiHider;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
<#if parentActivityClass != "">
import android.view.MenuItem;
import android.support.v4.app.NavUtils;
</#if>
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*
* @see SystemUiHider
*/
public class ${activityClass} extends Activity {
/**
* Whether or not the system UI should be auto-hidden after
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
*/
private static final boolean AUTO_HIDE = true;
/**
* If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
* user interaction before hiding the system UI.
*/
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
/**
* If set, will toggle the system UI visibility upon interaction. Otherwise,
* will show the system UI visibility upon interaction.
*/
private static final boolean TOGGLE_ON_CLICK = true;
/**
* The flags to pass to {@link SystemUiHider#getInstance}.
*/
private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
/**
* The instance of the {@link SystemUiHider} for this activity.
*/
private SystemUiHider mSystemUiHider;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.${layoutName});
<#if parentActivityClass != "">
setupActionBar();
</#if>
final View controlsView = findViewById(R.id.fullscreen_content_controls);
final View contentView = findViewById(R.id.fullscreen_content);
// Set up an instance of SystemUiHider to control the system UI for
// this activity.
mSystemUiHider = SystemUiHider.getInstance(this, contentView, HIDER_FLAGS);
mSystemUiHider.setup();
mSystemUiHider
.setOnVisibilityChangeListener(new SystemUiHider.OnVisibilityChangeListener() {
// Cached values.
int mControlsHeight;
int mShortAnimTime;
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public void onVisibilityChange(boolean visible) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
// If the ViewPropertyAnimator API is available
// (Honeycomb MR2 and later), use it to animate the
// in-layout UI controls at the bottom of the
// screen.
if (mControlsHeight == 0) {
mControlsHeight = controlsView.getHeight();
}
if (mShortAnimTime == 0) {
mShortAnimTime = getResources().getInteger(
android.R.integer.config_shortAnimTime);
}
controlsView.animate()
.translationY(visible ? 0 : mControlsHeight)
.setDuration(mShortAnimTime);
} else {
// If the ViewPropertyAnimator APIs aren't
// available, simply show or hide the in-layout UI
// controls.
controlsView.setVisibility(visible ? View.VISIBLE : View.GONE);
}
if (visible && AUTO_HIDE) {
// Schedule a hide().
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
}
});
// Set up the user interaction to manually show or hide the system UI.
contentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (TOGGLE_ON_CLICK) {
mSystemUiHider.toggle();
} else {
mSystemUiHider.show();
}
}
});
// Upon interacting with UI controls, delay any scheduled hide()
// operations to prevent the jarring behavior of controls going away
// while interacting with the UI.
findViewById(R.id.dummy_button).setOnTouchListener(mDelayHideTouchListener);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Trigger the initial hide() shortly after the activity has been
// created, to briefly hint to the user that UI controls
// are available.
delayedHide(100);
}
<#if parentActivityClass != "">
/**
* Set up the {@link android.app.ActionBar}, if the API is available.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void setupActionBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// Show the Up button in the action bar.
getActionBar().setDisplayHomeAsUpEnabled(true);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
// TODO: If Settings has multiple levels, Up should navigate up
// that hierarchy.
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
</#if>
/**
* Touch listener to use for in-layout UI controls to delay hiding the
* system UI. This is to prevent the jarring behavior of controls going away
* while interacting with activity UI.
*/
View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (AUTO_HIDE) {
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
return false;
}
};
Handler mHideHandler = new Handler();
Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
mSystemUiHider.hide();
}
};
/**
* Schedules a call to hide() in [delay] milliseconds, canceling any
* previously scheduled calls.
*/
private void delayedHide(int delayMillis) {
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, delayMillis);
}
}

View File

@@ -0,0 +1,172 @@
package ${packageName}.util;
import android.app.Activity;
import android.os.Build;
import android.view.View;
/**
* A utility class that helps with showing and hiding system UI such as the
* status bar and navigation/system bar. This class uses backward-compatibility
* techniques described in <a href=
* "http://developer.android.com/training/backward-compatible-ui/index.html">
* Creating Backward-Compatible UIs</a> to ensure that devices running any
* version of ndroid OS are supported. More specifically, there are separate
* implementations of this abstract class: for newer devices,
* {@link #getInstance} will return a {@link SystemUiHiderHoneycomb} instance,
* while on older devices {@link #getInstance} will return a
* {@link SystemUiHiderBase} instance.
* <p>
* For more on system bars, see <a href=
* "http://developer.android.com/design/get-started/ui-overview.html#system-bars"
* > System Bars</a>.
*
* @see android.view.View#setSystemUiVisibility(int)
* @see android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN
*/
public abstract class SystemUiHider {
/**
* When this flag is set, the
* {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}
* flag will be set on older devices, making the status bar "float" on top
* of the activity layout. This is most useful when there are no controls at
* the top of the activity layout.
* <p>
* This flag isn't used on newer devices because the <a
* href="http://developer.android.com/design/patterns/actionbar.html">action
* bar</a>, the most important structural element of an Android app, should
* be visible and not obscured by the system UI.
*/
public static final int FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES = 0x1;
/**
* When this flag is set, {@link #show()} and {@link #hide()} will toggle
* the visibility of the status bar. If there is a navigation bar, show and
* hide will toggle low profile mode.
*/
public static final int FLAG_FULLSCREEN = 0x2;
/**
* When this flag is set, {@link #show()} and {@link #hide()} will toggle
* the visibility of the navigation bar, if it's present on the device and
* the device allows hiding it. In cases where the navigation bar is present
* but cannot be hidden, show and hide will toggle low profile mode.
*/
public static final int FLAG_HIDE_NAVIGATION = FLAG_FULLSCREEN | 0x4;
/**
* The activity associated with this UI hider object.
*/
protected Activity mActivity;
/**
* The view on which {@link View#setSystemUiVisibility(int)} will be called.
*/
protected View mAnchorView;
/**
* The current UI hider flags.
*
* @see #FLAG_FULLSCREEN
* @see #FLAG_HIDE_NAVIGATION
* @see #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES
*/
protected int mFlags;
/**
* The current visibility callback.
*/
protected OnVisibilityChangeListener mOnVisibilityChangeListener = sDummyListener;
/**
* Creates and returns an instance of {@link SystemUiHider} that is
* appropriate for this device. The object will be either a
* {@link SystemUiHiderBase} or {@link SystemUiHiderHoneycomb} depending on
* the device.
*
* @param activity The activity whose window's system UI should be
* controlled by this class.
* @param anchorView The view on which
* {@link View#setSystemUiVisibility(int)} will be called.
* @param flags Either 0 or any combination of {@link #FLAG_FULLSCREEN},
* {@link #FLAG_HIDE_NAVIGATION}, and
* {@link #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES}.
*/
public static SystemUiHider getInstance(Activity activity, View anchorView, int flags) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return new SystemUiHiderHoneycomb(activity, anchorView, flags);
} else {
return new SystemUiHiderBase(activity, anchorView, flags);
}
}
protected SystemUiHider(Activity activity, View anchorView, int flags) {
mActivity = activity;
mAnchorView = anchorView;
mFlags = flags;
}
/**
* Sets up the system UI hider. Should be called from
* {@link Activity#onCreate}.
*/
public abstract void setup();
/**
* Returns whether or not the system UI is visible.
*/
public abstract boolean isVisible();
/**
* Hide the system UI.
*/
public abstract void hide();
/**
* Show the system UI.
*/
public abstract void show();
/**
* Toggle the visibility of the system UI.
*/
public void toggle() {
if (isVisible()) {
hide();
} else {
show();
}
}
/**
* Registers a callback, to be triggered when the system UI visibility
* changes.
*/
public void setOnVisibilityChangeListener(OnVisibilityChangeListener listener) {
if (listener == null) {
listener = sDummyListener;
}
mOnVisibilityChangeListener = listener;
}
/**
* A dummy no-op callback for use when there is no other listener set.
*/
private static OnVisibilityChangeListener sDummyListener = new OnVisibilityChangeListener() {
@Override
public void onVisibilityChange(boolean visible) {
}
};
/**
* A callback interface used to listen for system UI visibility changes.
*/
public interface OnVisibilityChangeListener {
/**
* Called when the system UI visibility has changed.
*
* @param visible True if the system UI is visible.
*/
public void onVisibilityChange(boolean visible);
}
}

View File

@@ -0,0 +1,63 @@
package ${packageName}.util;
import android.app.Activity;
import android.view.View;
import android.view.WindowManager;
/**
* A base implementation of {@link SystemUiHider}. Uses APIs available in all
* API levels to show and hide the status bar.
*/
public class SystemUiHiderBase extends SystemUiHider {
/**
* Whether or not the system UI is currently visible. This is a cached value
* from calls to {@link #hide()} and {@link #show()}.
*/
private boolean mVisible = true;
/**
* Constructor not intended to be called by clients. Use
* {@link SystemUiHider#getInstance} to obtain an instance.
*/
protected SystemUiHiderBase(Activity activity, View anchorView, int flags) {
super(activity, anchorView, flags);
}
@Override
public void setup() {
if ((mFlags & FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES) == 0) {
mActivity.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
}
@Override
public boolean isVisible() {
return mVisible;
}
@Override
public void hide() {
if ((mFlags & FLAG_FULLSCREEN) != 0) {
mActivity.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
mOnVisibilityChangeListener.onVisibilityChange(false);
mVisible = false;
}
@Override
public void show() {
if ((mFlags & FLAG_FULLSCREEN) != 0) {
mActivity.getWindow().setFlags(
0,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
mOnVisibilityChangeListener.onVisibilityChange(true);
mVisible = true;
}
}

View File

@@ -0,0 +1,133 @@
package ${packageName}.util;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.view.View;
import android.view.WindowManager;
/**
* An API 11+ implementation of {@link SystemUiHider}. Uses APIs available in
* Honeycomb and later (specifically {@link View#setSystemUiVisibility(int)}) to
* show and hide the system UI.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class SystemUiHiderHoneycomb extends SystemUiHiderBase {
/**
* Flags for {@link View#setSystemUiVisibility(int)} to use when showing the
* system UI.
*/
private int mShowFlags;
/**
* Flags for {@link View#setSystemUiVisibility(int)} to use when hiding the
* system UI.
*/
private int mHideFlags;
/**
* Flags to test against the first parameter in
* {@link android.view.View.OnSystemUiVisibilityChangeListener#onSystemUiVisibilityChange(int)}
* to determine the system UI visibility state.
*/
private int mTestFlags;
/**
* Whether or not the system UI is currently visible. This is cached from
* {@link android.view.View.OnSystemUiVisibilityChangeListener}.
*/
private boolean mVisible = true;
/**
* Constructor not intended to be called by clients. Use
* {@link SystemUiHider#getInstance} to obtain an instance.
*/
protected SystemUiHiderHoneycomb(Activity activity, View anchorView, int flags) {
super(activity, anchorView, flags);
mShowFlags = View.SYSTEM_UI_FLAG_VISIBLE;
mHideFlags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
mTestFlags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
if ((mFlags & FLAG_FULLSCREEN) != 0) {
// If the client requested fullscreen, add flags relevant to hiding
// the status bar. Note that some of these constants are new as of
// API 16 (Jelly Bean). It is safe to use them, as they are inlined
// at compile-time and do nothing on pre-Jelly Bean devices.
mShowFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
mHideFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN;
}
if ((mFlags & FLAG_HIDE_NAVIGATION) != 0) {
// If the client requested hiding navigation, add relevant flags.
mShowFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
mHideFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
mTestFlags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
}
}
/** {@inheritDoc} */
@Override
public void setup() {
mAnchorView.setOnSystemUiVisibilityChangeListener(mSystemUiVisibilityChangeListener);
}
/** {@inheritDoc} */
@Override
public void hide() {
mAnchorView.setSystemUiVisibility(mHideFlags);
}
/** {@inheritDoc} */
@Override
public void show() {
mAnchorView.setSystemUiVisibility(mShowFlags);
}
/** {@inheritDoc} */
@Override
public boolean isVisible() {
return mVisible;
}
private View.OnSystemUiVisibilityChangeListener mSystemUiVisibilityChangeListener
= new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int vis) {
// Test against mTestFlags to see if the system UI is visible.
if ((vis & mTestFlags) != 0) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
// Pre-Jelly Bean, we must manually hide the action bar
// and use the old window flags API.
mActivity.getActionBar().hide();
mActivity.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
// Trigger the registered listener and cache the visibility
// state.
mOnVisibilityChangeListener.onVisibilityChange(false);
mVisible = false;
} else {
mAnchorView.setSystemUiVisibility(mShowFlags);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
// Pre-Jelly Bean, we must manually show the action bar
// and use the old window flags API.
mActivity.getActionBar().show();
mActivity.getWindow().setFlags(
0,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
// Trigger the registered listener and cache the visibility
// state.
mOnVisibilityChangeListener.onVisibilityChange(true);
mVisible = true;
}
}
};
}

View File

@@ -0,0 +1,68 @@
<?xml version="1.0"?>
<template
format="4"
revision="4"
name="Fullscreen Activity"
description="Creates a new activity that toggles the visibility of the system UI (status and navigation bars) and action bar upon user interaction."
minApi="4"
minBuildApi="16">
<dependency name="android-support-v4" revision="8" />
<category value="Activity" />
<formfactor value="Mobile" />
<parameter
id="activityClass"
name="Activity Name"
type="string"
constraints="class|unique|nonempty"
default="FullscreenActivity"
help="The name of the activity class to create" />
<parameter
id="layoutName"
name="Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="${activityToLayout(activityClass)}"
default="activity_fullscreen"
help="The name of the layout to create for the activity" />
<parameter
id="activityTitle"
name="Title"
type="string"
constraints="nonempty"
default="FullscreenActivity"
suggest="${activityClass}"
help="The name of the activity." />
<parameter
id="isLauncher"
name="Launcher Activity"
type="boolean"
default="false"
help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
<parameter
id="parentActivityClass"
name="Hierarchical Parent"
type="string"
constraints="activity|exists|empty"
default=""
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
<parameter
id="packageName"
name="Package name"
type="string"
constraints="package"
default="com.mycompany.myapp" />
<thumbs>
<thumb>template_fullscreen_activity.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<globals>
<global id="projectOut" value="." />
<global id="manifestOut" value="${manifestDir}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="resOut" value="${resDir}" />
<global id="menuName" value="${classToResource(activityClass)}" />
<global id="simpleName" value="${activityToLayout(activityClass)}" />
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0"?>
<recipe>
<dependency mavenUrl="com.google.android.gms:play-services:4.2.42" />
<dependency mavenUrl="com.android.support:appcompat-v7:19.+" />
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<merge from="res/values/dimens.xml"
to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
<instantiate from="res/layout/activity_login.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
<instantiate from="res/values/strings.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/strings_${simpleName}.xml" />
<instantiate from="src/app_package/LoginActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<#if includeGooglePlus>
<instantiate from="src/app_package/PlusBaseActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/PlusBaseActivity.java" />
</#if>
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
</recipe>

View File

@@ -0,0 +1,37 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<#if includeGooglePlus>
<!-- To access Google+ APIs: -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- To retrieve OAuth 2.0 tokens or invalidate tokens to disconnect a user. This disconnect
option is required to comply with the Google+ Sign-In developer policies -->
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<!-- To retrieve the account name (email) as part of sign-in: -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" /></#if>
<!-- To auto-complete the email text field in the login form with the user's emails --><#if !includeGooglePlus>
<uses-permission android:name="android.permission.GET_ACCOUNTS" /></#if>
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<application>
<activity android:name=".${activityClass}"
<#if isNewProject>
android:label="@string/app_name"
<#else>
android:label="@string/title_${simpleName}"
</#if>
android:windowSoftInputMode="adjustResize|<#if includeGooglePlus>stateHidden<#else>stateVisible</#if>"
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
<#if parentActivityClass != "">
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="${parentActivityClass}" />
</#if>
</activity>
<#if includeGooglePlus>
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
</#if>
</application>
</manifest>

View File

@@ -0,0 +1,108 @@
<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:gravity="center_horizontal"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="${relativePackage}.${activityClass}">
<!-- Login progress -->
<ProgressBar
android:id="@+id/login_progress"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="gone"/>
<ScrollView
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<#if includeGooglePlus>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.gms.common.SignInButton
android:id="@+id/plus_sign_in_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"/>
<LinearLayout
android:id="@+id/plus_sign_out_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:weightSum="2">
<Button
android:id="@+id/plus_sign_out_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/plus_sign_out"/>
<Button
android:id="@+id/plus_disconnect_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/plus_disconnect"/>
</LinearLayout>
</#if>
<LinearLayout
android:id="@+id/email_login_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<AutoCompleteTextView
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:maxLines="1"
android:singleLine="true"/>
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_password"
android:imeActionId="@+id/login"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true"/>
<Button
android:id="@+id/email_sign_in_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_sign_in"
android:textStyle="bold"/>
</LinearLayout>
<#if includeGooglePlus>
</LinearLayout>
</#if>
</ScrollView>
</LinearLayout>

View File

@@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,17 @@
<resources>
<#if !isNewProject>
<string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
</#if>
<!-- Strings related to login -->
<string name="prompt_email">Email</string>
<string name="prompt_password">Password (optional)</string>
<string name="action_sign_in">Sign in or register</string>
<string name="action_sign_in_short">Sign in</string>
<#if includeGooglePlus> <string name="plus_sign_out">Switch Google+ account</string>
<string name="plus_disconnect">Disconnect from Google+</string></#if>
<string name="error_invalid_email">This email address is invalid</string>
<string name="error_invalid_password">This password is too short</string>
<string name="error_incorrect_password">This password is incorrect</string>
<string name="error_field_required">This field is required</string>
</resources>

View File

@@ -0,0 +1,449 @@
package ${packageName};
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
<#if !includeGooglePlus>import android.app.Activity;</#if>
import android.app.LoaderManager.LoaderCallbacks;
import android.content.ContentResolver;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
<#if minApiLevel lt 14>import android.os.Build.VERSION;</#if>
import android.os.Build;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
<#if includeGooglePlus>
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.SignInButton;
</#if>
import java.util.ArrayList;
import java.util.List;
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* A login screen that offers login via email/password<#if includeGooglePlus> and via Google+ sign in</#if>.
<#if includeGooglePlus> * <p/>
* ************ IMPORTANT SETUP NOTES: ************
* In order for Google+ sign in to work with your app, you must first go to:
* https://developers.google.com/+/mobile/android/getting-started#step_1_enable_the_google_api
* and follow the steps in "Step 1" to create an OAuth 2.0 client for your package.</#if>
*/
public class ${activityClass} extends <#if includeGooglePlus>PlusBase</#if>Activity implements LoaderCallbacks<Cursor>{
/**
* A dummy authentication store containing known user names and passwords.
* TODO: remove after connecting to a real authentication system.
*/
private static final String[] DUMMY_CREDENTIALS = new String[]{
"foo@example.com:hello", "bar@example.com:world"
};
/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
private UserLoginTask mAuthTask = null;
// UI references.
private AutoCompleteTextView mEmailView;
private EditText mPasswordView;
private View mProgressView;<#if includeGooglePlus>
private View mEmailLoginFormView;
private SignInButton mPlusSignInButton;
private View mSignOutButtons;</#if>
private View mLoginFormView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
<#if parentActivityClass != "">
setupActionBar();
</#if>
<#if includeGooglePlus>
// Find the Google+ sign in button.
mPlusSignInButton = (SignInButton) findViewById(R.id.plus_sign_in_button);
if (supportsGooglePlayServices()) {
// Set a listener to connect the user when the G+ button is clicked.
mPlusSignInButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
signIn();
}
});
} else {
// Don't offer G+ sign in if the app's version is too low to support Google Play
// Services.
mPlusSignInButton.setVisibility(View.GONE);
return;
}
</#if>
// Set up the login form.
mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
populateAutoComplete();
mPasswordView = (EditText) findViewById(R.id.password);
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
}
});
Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
mEmailSignInButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
attemptLogin();
}
});
mLoginFormView = findViewById(R.id.login_form);
mProgressView = findViewById(R.id.login_progress);<#if includeGooglePlus>
mEmailLoginFormView = findViewById(R.id.email_login_form);
mSignOutButtons = findViewById(R.id.plus_sign_out_buttons);</#if>
}
private void populateAutoComplete() {
<#if minApiLevel gte 14>
getLoaderManager().initLoader(0, null, this);
<#else>
if (VERSION.SDK_INT >= 14) {
// Use ContactsContract.Profile (API 14+)
getLoaderManager().initLoader(0, null, this);
} else if (VERSION.SDK_INT >= 8) {
// Use AccountManager (API 8+)
new SetupEmailAutoCompleteTask().execute(null, null);
}
</#if>
}
<#if parentActivityClass != "">
/**
* Set up the {@link android.app.ActionBar}, if the API is available.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void setupActionBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// Show the Up button in the action bar.
getActionBar().setDisplayHomeAsUpEnabled(true);
}
}
</#if>
/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
public void attemptLogin() {
if (mAuthTask != null) {
return;
}
// Reset errors.
mEmailView.setError(null);
mPasswordView.setError(null);
// Store values at the time of the login attempt.
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();
boolean cancel = false;
View focusView = null;
// Check for a valid password, if the user entered one.
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
mPasswordView.setError(getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}
// Check for a valid email address.
if (TextUtils.isEmpty(email)) {
mEmailView.setError(getString(R.string.error_field_required));
focusView = mEmailView;
cancel = true;
} else if (!isEmailValid(email)) {
mEmailView.setError(getString(R.string.error_invalid_email));
focusView = mEmailView;
cancel = true;
}
if (cancel) {
// There was an error; don't attempt login and focus the first
// form field with an error.
focusView.requestFocus();
} else {
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
showProgress(true);
mAuthTask = new UserLoginTask(email, password);
mAuthTask.execute((Void) null);
}
}
private boolean isEmailValid(String email) {
//TODO: Replace this with your own logic
return email.contains("@");
}
private boolean isPasswordValid(String password) {
//TODO: Replace this with your own logic
return password.length() > 4;
}
/**
* Shows the progress UI and hides the login form.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public void showProgress(final boolean show) {
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
// for very easy animations. If available, use these APIs to fade-in
// the progress spinner.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mProgressView.animate().setDuration(shortAnimTime).alpha(
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
} else {
// The ViewPropertyAnimator APIs are not available, so simply show
// and hide the relevant UI components.
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
<#if includeGooglePlus>
@Override
protected void onPlusClientSignIn() {
//Set up sign out and disconnect buttons.
Button signOutButton = (Button) findViewById(R.id.plus_sign_out_button);
signOutButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
signOut();
}
});
Button disconnectButton = (Button) findViewById(R.id.plus_disconnect_button);
disconnectButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
revokeAccess();
}
});
}
@Override
protected void onPlusClientBlockingUI(boolean show) {
showProgress(show);
}
@Override
protected void updateConnectButtonState() {
//TODO: Update this logic to also handle the user logged in by email.
boolean connected = getPlusClient().isConnected();
mSignOutButtons.setVisibility(connected ? View.VISIBLE : View.GONE);
mPlusSignInButton.setVisibility(connected ? View.GONE : View.VISIBLE);
mEmailLoginFormView.setVisibility(connected ? View.GONE : View.VISIBLE);
}
@Override
protected void onPlusClientRevokeAccess() {
// TODO: Access to the user's G+ account has been revoked. Per the developer terms, delete
// any stored user data here.
}
@Override
protected void onPlusClientSignOut() {
}
/**
* Check if the device supports Google Play Services. It's best
* practice to check first rather than handling this as an error case.
*
* @return whether the device supports Google Play Services
*/
private boolean supportsGooglePlayServices() {
return GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) ==
ConnectionResult.SUCCESS;
}
</#if>
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this,
// Retrieve data rows for the device user's 'profile' contact.
Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
// Select only email addresses.
ContactsContract.Contacts.Data.MIMETYPE +
" = ?", new String[]{ContactsContract.CommonDataKinds.Email
.CONTENT_ITEM_TYPE},
// Show primary email addresses first. Note that there won't be
// a primary email address if the user hasn't specified one.
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
List<String> emails = new ArrayList<String>();
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
emails.add(cursor.getString(ProfileQuery.ADDRESS));
cursor.moveToNext();
}
addEmailsToAutoComplete(emails);
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
}
private interface ProfileQuery {
String[] PROJECTION = {
ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
};
int ADDRESS = 0;
int IS_PRIMARY = 1;
}
<#if minApiLevel lt 14>
/**
* Use an AsyncTask to fetch the user's email addresses on a background thread, and update
* the email text field with results on the main UI thread.
*/
class SetupEmailAutoCompleteTask extends AsyncTask<Void, Void, List<String>> {
@Override
protected List<String> doInBackground(Void... voids) {
ArrayList<String> emailAddressCollection = new ArrayList<String>();
// Get all emails from the user's contacts and copy them to a list.
ContentResolver cr = getContentResolver();
Cursor emailCur = cr.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
null, null, null);
while (emailCur.moveToNext()) {
String email = emailCur.getString(emailCur.getColumnIndex(ContactsContract
.CommonDataKinds.Email.DATA));
emailAddressCollection.add(email);
}
emailCur.close();
return emailAddressCollection;
}
@Override
protected void onPostExecute(List<String> emailAddressCollection) {
addEmailsToAutoComplete(emailAddressCollection);
}
}
</#if>
private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
//Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
ArrayAdapter<String> adapter =
new ArrayAdapter<String>(LoginActivity.this,
android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
mEmailView.setAdapter(adapter);
}
/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
private final String mEmail;
private final String mPassword;
UserLoginTask(String email, String password) {
mEmail = email;
mPassword = password;
}
@Override
protected Boolean doInBackground(Void... params) {
// TODO: attempt authentication against a network service.
try {
// Simulate network access.
Thread.sleep(2000);
} catch (InterruptedException e) {
return false;
}
for (String credential : DUMMY_CREDENTIALS) {
String[] pieces = credential.split(":");
if (pieces[0].equals(mEmail)) {
// Account exists, return true if the password matches.
return pieces[1].equals(mPassword);
}
}
// TODO: register the new account here.
return true;
}
@Override
protected void onPostExecute(final Boolean success) {
mAuthTask = null;
showProgress(false);
if (success) {
finish();
} else {
mPasswordView.setError(getString(R.string.error_incorrect_password));
mPasswordView.requestFocus();
}
}
@Override
protected void onCancelled() {
mAuthTask = null;
showProgress(false);
}
}
}

View File

@@ -0,0 +1,282 @@
package ${packageName};
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
<#if minApiLevel lt 14>import android.support.v7.app.ActionBarActivity;</#if>
<#if minApiLevel gte 14>import android.app.Activity;</#if>
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.Scopes;
import com.google.android.gms.plus.PlusClient;
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* A base class to wrap communication with the Google Play Services PlusClient.
*/
public abstract class PlusBaseActivity extends <#if minApiLevel lt 14>ActionBar</#if>Activity
implements GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener {
private static final String TAG = PlusBaseActivity.class.getSimpleName();
// A magic number we will use to know that our sign-in error resolution activity has completed
private static final int OUR_REQUEST_CODE = 49404;
// A flag to stop multiple dialogues appearing for the user
private boolean mAutoResolveOnFail;
// A flag to track when a connection is already in progress
public boolean mPlusClientIsConnecting = false;
// This is the helper object that connects to Google Play Services.
private PlusClient mPlusClient;
// The saved result from {@link #onConnectionFailed(ConnectionResult)}. If a connection
// attempt has been made, this is non-null.
// If this IS null, then the connect method is still running.
private ConnectionResult mConnectionResult;
/**
* Called when the {@link PlusClient} revokes access to this app.
*/
protected abstract void onPlusClientRevokeAccess();
/**
* Called when the PlusClient is successfully connected.
*/
protected abstract void onPlusClientSignIn();
/**
* Called when the {@link PlusClient} is disconnected.
*/
protected abstract void onPlusClientSignOut();
/**
* Called when the {@link PlusClient} is blocking the UI. If you have a progress bar widget,
* this tells you when to show or hide it.
*/
protected abstract void onPlusClientBlockingUI(boolean show);
/**
* Called when there is a change in connection state. If you have "Sign in"/ "Connect",
* "Sign out"/ "Disconnect", or "Revoke access" buttons, this lets you know when their states
* need to be updated.
*/
protected abstract void updateConnectButtonState();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Initialize the PlusClient connection.
// Scopes indicate the information about the user your application will be able to access.
mPlusClient =
new PlusClient.Builder(this, this, this).setScopes(Scopes.PLUS_LOGIN,
Scopes.PLUS_ME).build();
}
/**
* Try to sign in the user.
*/
public void signIn() {
if (!mPlusClient.isConnected()) {
// Show the dialog as we are now signing in.
setProgressBarVisible(true);
// Make sure that we will start the resolution (e.g. fire the intent and pop up a
// dialog for the user) for any errors that come in.
mAutoResolveOnFail = true;
// We should always have a connection result ready to resolve,
// so we can start that process.
if (mConnectionResult != null) {
startResolution();
} else {
// If we don't have one though, we can start connect in
// order to retrieve one.
initiatePlusClientConnect();
}
}
updateConnectButtonState();
}
/**
* Connect the {@link PlusClient} only if a connection isn't already in progress. This will
* call back to {@link #onConnected(android.os.Bundle)} or
* {@link #onConnectionFailed(com.google.android.gms.common.ConnectionResult)}.
*/
private void initiatePlusClientConnect() {
if (!mPlusClient.isConnected() && !mPlusClient.isConnecting()) {
mPlusClient.connect();
}
}
/**
* Disconnect the {@link PlusClient} only if it is connected (otherwise, it can throw an error.)
* This will call back to {@link #onDisconnected()}.
*/
private void initiatePlusClientDisconnect() {
if (mPlusClient.isConnected()) {
mPlusClient.disconnect();
}
}
/**
* Sign out the user (so they can switch to another account).
*/
public void signOut() {
// We only want to sign out if we're connected.
if (mPlusClient.isConnected()) {
// Clear the default account in order to allow the user to potentially choose a
// different account from the account chooser.
mPlusClient.clearDefaultAccount();
// Disconnect from Google Play Services, then reconnect in order to restart the
// process from scratch.
initiatePlusClientDisconnect();
Log.v(TAG, "Sign out successful!");
}
updateConnectButtonState();
}
/**
* Revoke Google+ authorization completely.
*/
public void revokeAccess() {
if (mPlusClient.isConnected()) {
// Clear the default account as in the Sign Out.
mPlusClient.clearDefaultAccount();
// Revoke access to this entire application. This will call back to
// onAccessRevoked when it is complete, as it needs to reach the Google
// authentication servers to revoke all tokens.
mPlusClient.revokeAccessAndDisconnect(new PlusClient.OnAccessRevokedListener() {
public void onAccessRevoked(ConnectionResult result) {
updateConnectButtonState();
onPlusClientRevokeAccess();
}
});
}
}
@Override
protected void onStart() {
super.onStart();
initiatePlusClientConnect();
}
@Override
protected void onStop() {
super.onStop();
initiatePlusClientDisconnect();
}
public boolean isPlusClientConnecting() {
return mPlusClientIsConnecting;
}
private void setProgressBarVisible(boolean flag) {
mPlusClientIsConnecting = flag;
onPlusClientBlockingUI(flag);
}
/**
* A helper method to flip the mResolveOnFail flag and start the resolution
* of the ConnectionResult from the failed connect() call.
*/
private void startResolution() {
try {
// Don't start another resolution now until we have a result from the activity we're
// about to start.
mAutoResolveOnFail = false;
// If we can resolve the error, then call start resolution and pass it an integer tag
// we can use to track.
// This means that when we get the onActivityResult callback we'll know it's from
// being started here.
mConnectionResult.startResolutionForResult(this, OUR_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
// Any problems, just try to connect() again so we get a new ConnectionResult.
mConnectionResult = null;
initiatePlusClientConnect();
}
}
/**
* An earlier connection failed, and we're now receiving the result of the resolution attempt
* by PlusClient.
*
* @see #onConnectionFailed(ConnectionResult)
*/
@Override
protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
updateConnectButtonState();
if (requestCode == OUR_REQUEST_CODE && responseCode == RESULT_OK) {
// If we have a successful result, we will want to be able to resolve any further
// errors, so turn on resolution with our flag.
mAutoResolveOnFail = true;
// If we have a successful result, let's call connect() again. If there are any more
// errors to resolve we'll get our onConnectionFailed, but if not,
// we'll get onConnected.
initiatePlusClientConnect();
} else if (requestCode == OUR_REQUEST_CODE && responseCode != RESULT_OK) {
// If we've got an error we can't resolve, we're no longer in the midst of signing
// in, so we can stop the progress spinner.
setProgressBarVisible(false);
}
}
/**
* Successfully connected (called by PlusClient)
*/
@Override
public void onConnected(Bundle connectionHint) {
updateConnectButtonState();
setProgressBarVisible(false);
onPlusClientSignIn();
}
/**
* Successfully disconnected (called by PlusClient)
*/
@Override
public void onDisconnected() {
updateConnectButtonState();
onPlusClientSignOut();
}
/**
* Connection failed for some reason (called by PlusClient)
* Try and resolve the result. Failure here is usually not an indication of a serious error,
* just that the user's input is needed.
*
* @see #onActivityResult(int, int, Intent)
*/
@Override
public void onConnectionFailed(ConnectionResult result) {
updateConnectButtonState();
// Most of the time, the connection will fail with a user resolvable result. We can store
// that in our mConnectionResult property ready to be used when the user clicks the
// sign-in button.
if (result.hasResolution()) {
mConnectionResult = result;
if (mAutoResolveOnFail) {
// This is a local helper function that starts the resolution of the problem,
// which may be showing the user an account chooser or similar.
startResolution();
}
}
}
public PlusClient getPlusClient() {
return mPlusClient;
}
}

View File

@@ -0,0 +1,68 @@
<?xml version="1.0"?>
<template
format="4"
revision="5"
name="Login Activity"
description="Creates a new login activity, allowing users to optionally sign in with Google+ or enter an email address and password to log in to or register with your application."
minApi="8"
minBuildApi="14">
<dependency name="android-support-v4" revision="8" />
<category value="Activity" />
<formfactor value="Mobile" />
<parameter
id="activityClass"
name="Activity Name"
type="string"
constraints="class|unique|nonempty"
default="LoginActivity"
help="The name of the activity class to create" />
<parameter
id="layoutName"
name="Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="${activityToLayout(activityClass)}"
default="activity_login"
help="The name of the layout to create for the activity" />
<parameter
id="activityTitle"
name="Title"
type="string"
constraints="nonempty"
default="Sign in"
help="The name of the activity." />
<parameter
id="parentActivityClass"
name="Hierarchical Parent"
type="string"
constraints="activity|exists|empty"
default=""
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
<parameter
id="packageName"
name="Package name"
type="string"
constraints="package"
default="com.mycompany.myapp" />
<parameter
id="includeGooglePlus"
name="Include Google+ sign in"
type="boolean"
default="true" />
<thumbs>
<thumb>template_login_activity.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<globals>
<global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
<global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
<global id="projectOut" value="." />
<global id="manifestOut" value="${manifestDir}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="resOut" value="${resDir}" />
<global id="CollectionName" value="${extractLetters(objectKind)}List" />
<global id="collection_name" value="${extractLetters(objectKind?lower_case)}_list" />
<global id="DetailName" value="${extractLetters(objectKind)}Detail" />
<global id="detail_name" value="${extractLetters(objectKind?lower_case)}_detail" />
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0"?>
<recipe>
<dependency mavenUrl="com.android.support:support-v4:19.+" />
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<merge from="res/values-large/refs.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values-large/refs.xml" />
<merge from="res/values-sw600dp/refs.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values-sw600dp/refs.xml" />
<merge from="res/values/strings.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
<instantiate from="res/layout/activity_content_detail.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/activity_${detail_name}.xml" />
<instantiate from="res/layout/activity_content_list.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/activity_${collection_name}.xml" />
<instantiate from="res/layout/activity_content_twopane.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/activity_${extractLetters(objectKind?lower_case)}_twopane.xml" />
<instantiate from="res/layout/fragment_content_detail.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/fragment_${detail_name}.xml" />
<instantiate from="src/app_package/ContentDetailActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${DetailName}Activity.java" />
<instantiate from="src/app_package/ContentDetailFragment.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
<instantiate from="src/app_package/ContentListActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${CollectionName}Activity.java" />
<instantiate from="src/app_package/ContentListFragment.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${CollectionName}Fragment.java" />
<instantiate from="src/app_package/dummy/DummyContent.java.ftl"
to="${escapeXmlAttribute(srcOut)}/dummy/DummyContent.java" />
<open file="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
<open file="${escapeXmlAttribute(resOut)}/layout/fragment_${detail_name}.xml" />
</recipe>

View File

@@ -0,0 +1,31 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name="${relativePackage}.${CollectionName}Activity"
<#if isNewProject>
android:label="@string/app_name"
<#else>
android:label="@string/title_${collection_name}"
</#if>
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
<#if parentActivityClass != "">
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="${parentActivityClass}" />
</#if>
<#if isLauncher>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</#if>
</activity>
<activity android:name="${relativePackage}.${DetailName}Activity"
android:label="@string/title_${detail_name}"
<#if buildApi gte 16>android:parentActivityName="${relativePackage}.${CollectionName}Activity"</#if>>
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="${relativePackage}.${CollectionName}Activity" />
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/${detail_name}_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${DetailName}Activity"
tools:ignore="MergeRootFrame" />

View File

@@ -0,0 +1,10 @@
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/${collection_name}"
android:name="${packageName}.${CollectionName}Fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
tools:context="${relativePackage}.${CollectionName}Activity"
tools:layout="@android:layout/list_content" />

View File

@@ -0,0 +1,38 @@
<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:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:baselineAligned="false"
android:divider="?android:attr/dividerHorizontal"
android:orientation="horizontal"
android:showDividers="middle"
tools:context="${relativePackage}.${CollectionName}Activity">
<!--
This layout is a two-pane layout for the ${objectKindPlural}
master/detail flow. See res/values-large/refs.xml and
res/values-sw600dp/refs.xml for an example of layout aliases
that replace the single-pane version of the layout with
this two-pane version.
For more on layout aliases, see:
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
-->
<fragment
android:id="@+id/${collection_name}"
android:name="${packageName}.${CollectionName}Fragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
tools:layout="@android:layout/list_content" />
<FrameLayout
android:id="@+id/${detail_name}_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3" />
</LinearLayout>

View File

@@ -0,0 +1,9 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/${detail_name}"
style="?android:attr/textAppearanceLarge"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:textIsSelectable="true"
tools:context="${relativePackage}.${DetailName}Fragment" />

View File

@@ -0,0 +1,10 @@
<resources>
<!--
Layout alias to replace the single-pane version of the layout with a
two-pane version on Large screens.
For more on layout aliases, see:
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
-->
<item type="layout" name="activity_${collection_name}">@layout/activity_${extractLetters(objectKind?lower_case)}_twopane</item>
</resources>

View File

@@ -0,0 +1,11 @@
<resources>
<!--
Layout alias to replace the single-pane version of the layout with a
two-pane version on screens with a smallest width (smallest dimension)
of at least 600 density-independent pixels (dips).
For more on layout aliases, see:
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
-->
<item type="layout" name="activity_${collection_name}">@layout/activity_${extractLetters(objectKind?lower_case)}_twopane</item>
</resources>

View File

@@ -0,0 +1,6 @@
<resources>
<#if !isNewProject>
<string name="title_${collection_name}">${escapeXmlString(objectKindPlural)}</string>
</#if>
<string name="title_${detail_name}">${escapeXmlString(objectKind)} Detail</string>
</resources>

View File

@@ -0,0 +1,78 @@
package ${packageName};
import android.content.Intent;
import android.os.Bundle;
import <#if appCompat>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
<#if minApiLevel lt 16>import android.support.v4.app.NavUtils;</#if>
import android.view.MenuItem;
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* An activity representing a single ${objectKind} detail screen. This
* activity is only used on handset devices. On tablet-size devices,
* item details are presented side-by-side with a list of items
* in a {@link ${CollectionName}Activity}.
* <p>
* This activity is mostly just a 'shell' activity containing nothing
* more than a {@link ${DetailName}Fragment}.
*/
public class ${DetailName}Activity extends ${appCompat?string('ActionBar','')}Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_${detail_name});
// Show the Up button in the action bar.
get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
// savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity
// (e.g. when rotating the screen from portrait to landscape).
// In this case, the fragment will automatically be re-added
// to its container so we don't need to manually add it.
// For more information, see the Fragments API guide at:
//
// http://developer.android.com/guide/components/fragments.html
//
if (savedInstanceState == null) {
// Create the detail fragment and add it to the activity
// using a fragment transaction.
Bundle arguments = new Bundle();
arguments.putString(${DetailName}Fragment.ARG_ITEM_ID,
getIntent().getStringExtra(${DetailName}Fragment.ARG_ITEM_ID));
${DetailName}Fragment fragment = new ${DetailName}Fragment();
fragment.setArguments(arguments);
get${Support}FragmentManager().beginTransaction()
.add(R.id.${detail_name}_container, fragment)
.commit();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
<#if minApiLevel lt 16>
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpTo(this, new Intent(this, ${CollectionName}Activity.class));
<#else>
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
navigateUpTo(new Intent(this, ${CollectionName}Activity.class));
</#if>
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -0,0 +1,62 @@
package ${packageName};
import android.os.Bundle;
import android.<#if appCompat>support.v4.</#if>app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
import ${packageName}.dummy.DummyContent;
/**
* A fragment representing a single ${objectKind} detail screen.
* This fragment is either contained in a {@link ${CollectionName}Activity}
* in two-pane mode (on tablets) or a {@link ${DetailName}Activity}
* on handsets.
*/
public class ${DetailName}Fragment extends Fragment {
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
public static final String ARG_ITEM_ID = "item_id";
/**
* The dummy content this fragment is presenting.
*/
private DummyContent.DummyItem mItem;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public ${DetailName}Fragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments().containsKey(ARG_ITEM_ID)) {
// Load the dummy content specified by the fragment
// arguments. In a real-world scenario, use a Loader
// to load content from a content provider.
mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_${detail_name}, container, false);
// Show the dummy content as text in a TextView.
if (mItem != null) {
((TextView) rootView.findViewById(R.id.${detail_name})).setText(mItem.content);
}
return rootView;
}
}

View File

@@ -0,0 +1,106 @@
package ${packageName};
import android.content.Intent;
import android.os.Bundle;
import <#if appCompat>android.support.v4.app.FragmentActivity<#else>android.app.Activity</#if>;
<#if (parentActivityClass != "" && minApiLevel lt 16)>import android.support.v4.app.NavUtils;</#if>
<#if parentActivityClass != "">import android.view.MenuItem;</#if>
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
/**
* An activity representing a list of ${objectKindPlural}. This activity
* has different presentations for handset and tablet-size devices. On
* handsets, the activity presents a list of items, which when touched,
* lead to a {@link ${DetailName}Activity} representing
* item details. On tablets, the activity presents the list of items and
* item details side-by-side using two vertical panes.
* <p>
* The activity makes heavy use of fragments. The list of items is a
* {@link ${CollectionName}Fragment} and the item details
* (if present) is a {@link ${DetailName}Fragment}.
* <p>
* This activity also implements the required
* {@link ${CollectionName}Fragment.Callbacks} interface
* to listen for item selections.
*/
public class ${CollectionName}Activity extends ${(appCompat)?string('Fragment','')}Activity
implements ${CollectionName}Fragment.Callbacks {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_${collection_name});
<#if parentActivityClass != "">
// Show the Up button in the action bar.
get${Support}ActionBar().setDisplayHomeAsUpEnabled(true);
</#if>
if (findViewById(R.id.${detail_name}_container) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
// In two-pane mode, list items should be given the
// 'activated' state when touched.
((${CollectionName}Fragment) get${Support}FragmentManager()
.findFragmentById(R.id.${collection_name}))
.setActivateOnItemClick(true);
}
// TODO: If exposing deep links into your app, handle intents here.
}
<#if parentActivityClass != "">
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
${(minApiLevel lt 16)?string('NavUtils.','')}navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
</#if>
/**
* Callback method from {@link ${CollectionName}Fragment.Callbacks}
* indicating that the item with the given ID was selected.
*/
@Override
public void onItemSelected(String id) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
Bundle arguments = new Bundle();
arguments.putString(${DetailName}Fragment.ARG_ITEM_ID, id);
${DetailName}Fragment fragment = new ${DetailName}Fragment();
fragment.setArguments(arguments);
get${Support}FragmentManager().beginTransaction()
.replace(R.id.${detail_name}_container, fragment)
.commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, ${DetailName}Activity.class);
detailIntent.putExtra(${DetailName}Fragment.ARG_ITEM_ID, id);
startActivity(detailIntent);
}
}
}

View File

@@ -0,0 +1,152 @@
package ${packageName};
import android.app.Activity;
import android.os.Bundle;
import android.<#if Support?has_content>support.v4.</#if>app.ListFragment;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
import ${packageName}.dummy.DummyContent;
/**
* A list fragment representing a list of ${objectKindPlural}. This fragment
* also supports tablet devices by allowing list items to be given an
* 'activated' state upon selection. This helps indicate which item is
* currently being viewed in a {@link ${DetailName}Fragment}.
* <p>
* Activities containing this fragment MUST implement the {@link Callbacks}
* interface.
*/
public class ${CollectionName}Fragment extends ListFragment {
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
*/
private static final String STATE_ACTIVATED_POSITION = "activated_position";
/**
* The fragment's current callback object, which is notified of list item
* clicks.
*/
private Callbacks mCallbacks = sDummyCallbacks;
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = ListView.INVALID_POSITION;
/**
* A callback interface that all activities containing this fragment must
* implement. This mechanism allows activities to be notified of item
* selections.
*/
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
public void onItemSelected(String id);
}
/**
* A dummy implementation of the {@link Callbacks} interface that does
* nothing. Used only when this fragment is not attached to an activity.
*/
private static Callbacks sDummyCallbacks = new Callbacks() {
@Override
public void onItemSelected(String id) {
}
};
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public ${CollectionName}Fragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: replace with a real list adapter.
setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(
getActivity(),
android.R.layout.simple_list_item_activated_1,
android.R.id.text1,
DummyContent.ITEMS));
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Restore the previously serialized activated item position.
if (savedInstanceState != null
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Activities containing this fragment must implement its callbacks.
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
mCallbacks = (Callbacks) activity;
}
@Override
public void onDetach() {
super.onDetach();
// Reset the active callbacks interface to the dummy implementation.
mCallbacks = sDummyCallbacks;
}
@Override
public void onListItemClick(ListView listView, View view, int position, long id) {
super.onListItemClick(listView, view, position, id);
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mActivatedPosition != ListView.INVALID_POSITION) {
// Serialize and persist the activated item position.
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
}
}
/**
* Turns on activate-on-click mode. When this mode is on, list items will be
* given the 'activated' state when touched.
*/
public void setActivateOnItemClick(boolean activateOnItemClick) {
// When setting CHOICE_MODE_SINGLE, ListView will automatically
// give items the 'activated' state when touched.
getListView().setChoiceMode(activateOnItemClick
? ListView.CHOICE_MODE_SINGLE
: ListView.CHOICE_MODE_NONE);
}
private void setActivatedPosition(int position) {
if (position == ListView.INVALID_POSITION) {
getListView().setItemChecked(mActivatedPosition, false);
} else {
getListView().setItemChecked(position, true);
}
mActivatedPosition = position;
}
}

View File

@@ -0,0 +1,55 @@
package ${packageName}.dummy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Helper class for providing sample content for user interfaces created by
* Android template wizards.
* <p>
* TODO: Replace all uses of this class before publishing your app.
*/
public class DummyContent {
/**
* An array of sample (dummy) items.
*/
public static List<DummyItem> ITEMS = new ArrayList<DummyItem>();
/**
* A map of sample (dummy) items, by ID.
*/
public static Map<String, DummyItem> ITEM_MAP = new HashMap<String, DummyItem>();
static {
// Add 3 sample items.
addItem(new DummyItem("1", "Item 1"));
addItem(new DummyItem("2", "Item 2"));
addItem(new DummyItem("3", "Item 3"));
}
private static void addItem(DummyItem item) {
ITEMS.add(item);
ITEM_MAP.put(item.id, item);
}
/**
* A dummy item representing a piece of content.
*/
public static class DummyItem {
public String id;
public String content;
public DummyItem(String id, String content) {
this.id = id;
this.content = content;
}
@Override
public String toString() {
return content;
}
}
}

View File

@@ -0,0 +1,68 @@
<?xml version="1.0"?>
<template
format="4"
revision="5"
name="Master/Detail Flow"
minApi="4"
description="Creates a new master/detail flow, allowing users to view a collection of objects as well as details for each object. This flow is presented using two columns on tablet-size screens and one column on handsets and smaller screens. This template creates two activities, a master fragment, and a detail fragment."
category="Activity">
<dependency name="android-support-v4" revision="8" />
<category value="Activity" />
<formfactor value="Mobile" />
<thumbs>
<thumb>template_master_detail.png</thumb>
</thumbs>
<parameter
id="objectKind"
name="Object Kind"
type="string"
constraints="nonempty"
default="Item"
help="Other examples are 'Person', 'Book', etc." />
<parameter
id="objectKindPlural"
name="Object Kind Plural"
type="string"
constraints="nonempty"
default="Items"
help="Other examples are 'People', 'Books', etc." />
<parameter
id="activityTitle"
name="Title"
type="string"
constraints="nonempty"
suggest="${objectKindPlural}"
default="Items" />
<parameter
id="isLauncher"
name="Launcher Activity"
type="boolean"
default="false"
help="If true, the primary activity in the flow will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
<parameter
id="parentActivityClass"
name="Hierarchical Parent"
type="string"
constraints="activity|exists|empty"
default=""
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
<parameter
id="packageName"
name="Package name"
type="string"
constraints="package"
default="com.mycompany.myapp" />
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<globals>
<global id="manifestOut" value="${manifestDir}" />
<global id="appCompat" type="boolean" value="${(minApiLevel lt 14)?string}" />
<!-- e.g. getSupportActionBar vs. getActionBar -->
<global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
<global id="ActionNamespace" value="${(minApiLevel lt 14)?string('app','android')}" />
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="resOut" value="${resDir}" />
<global id="menuName" value="${classToResource(activityClass)}" />
<global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0"?>
<recipe>
<#if appCompat><dependency mavenUrl="com.android.support:appcompat-v7:19.+"/></#if>
<#if !appCompat><dependency mavenUrl="com.android.support:support-v4:19.+"/></#if>
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<instantiate from="res/menu/main.xml.ftl"
to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />
<merge from="res/values/strings.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
<merge from="res/values/dimens.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
<merge from="res/values-w820dp/dimens.xml"
to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
<!-- TODO: switch on Holo Dark v. Holo Light -->
<copy from="res/drawable-hdpi"
to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
<copy from="res/drawable-mdpi"
to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
<copy from="res/drawable-xhdpi"
to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
<copy from="res/drawable-xxhdpi"
to="${escapeXmlAttribute(resOut)}/drawable-xxhdpi" />
<instantiate from="res/menu/global.xml.ftl"
to="${escapeXmlAttribute(resOut)}/menu/global.xml" />
<instantiate from="res/layout/activity_drawer.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
<instantiate from="res/layout/fragment_navigation_drawer.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${navigationDrawerLayout}.xml" />
<instantiate from="res/layout/fragment_simple.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
<instantiate from="src/app_package/DrawerActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<instantiate from="src/app_package/NavigationDrawerFragment.java.ftl"
to="${escapeXmlAttribute(srcOut)}/NavigationDrawerFragment.java" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
</recipe>

View File

@@ -0,0 +1,24 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
<activity android:name="${relativePackage}.${activityClass}"
<#if isNewProject>
android:label="@string/app_name"
<#else>
android:label="@string/title_${activityToLayout(activityClass)}"
</#if>
<#if buildApi gte 16 && parentActivityClass != "">android:parentActivityName="${parentActivityClass}"</#if>>
<#if parentActivityClass != "">
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="${parentActivityClass}" />
</#if>
<#if isLauncher>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</#if>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,7 @@
dependencies {
<#if dependencyList?? >
<#list dependencyList as dependency>
compile '${dependency}'
</#list>
</#if>
}

View File

@@ -0,0 +1,31 @@
<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. -->
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}">
<!-- As the main content view, the view below consumes the entire
space available using match_parent in both dimensions. -->
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- android:layout_gravity="start" tells DrawerLayout to treat
this as a sliding drawer on the left side for left-to-right
languages and on the right side for right-to-left languages.
If you're not building against API 17 or higher, use
android:layout_gravity="left" instead. -->
<!-- The drawer is given a fixed width in dp and extends the full height of
the container. -->
<fragment android:id="@+id/navigation_drawer"
android:layout_width="@dimen/navigation_drawer_width"
android:layout_height="match_parent"
android:layout_gravity="<#if buildApi gte 17>start<#else>left</#if>"
android:name="${packageName}.NavigationDrawerFragment"
tools:layout="@layout/${navigationDrawerLayout}" />
</android.support.v4.widget.DrawerLayout>

View File

@@ -0,0 +1,9 @@
<ListView 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:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:background="#cccc"
tools:context="${relativePackage}.NavigationDrawerFragment" />

View File

@@ -0,0 +1,16 @@
<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="${relativePackage}.${activityClass}$PlaceholderFragment">
<TextView
android:id="@+id/section_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>

View File

@@ -0,0 +1,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
xmlns:app="http://schemas.android.com/apk/res-auto"</#if>>
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
${ActionNamespace}:showAsAction="never" />
</menu>

View File

@@ -0,0 +1,12 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat>
xmlns:app="http://schemas.android.com/apk/res-auto"</#if>
xmlns:tools="http://schemas.android.com/tools"
tools:context="${relativePackage}.${activityClass}" >
<item android:id="@+id/action_example"
android:title="@string/action_example"
${ActionNamespace}:showAsAction="withText|ifRoom" />
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
${ActionNamespace}:showAsAction="never" />
</menu>

View File

@@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@@ -0,0 +1,9 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<!-- Per the design guidelines, navigation drawers should be between 240dp and 320dp:
https://developer.android.com/design/patterns/navigation-drawer.html -->
<dimen name="navigation_drawer_width">240dp</dimen>
</resources>

View File

@@ -0,0 +1,17 @@
<resources>
<#if !isNewProject>
<string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
</#if>
<string name="title_section1">Section 1</string>
<string name="title_section2">Section 2</string>
<string name="title_section3">Section 3</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="action_example">Example action</string>
<string name="action_settings">Settings</string>
</resources>

View File

@@ -0,0 +1,84 @@
package ${packageName};
import android.app.Activity;
<#if appCompat>import android.support.v7.app.ActionBarActivity;</#if>
import android.<#if appCompat>support.v7.</#if>app.ActionBar;
import android.<#if appCompat>support.v4.</#if>app.Fragment;
import android.<#if appCompat>support.v4.</#if>app.FragmentManager;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.support.v4.widget.DrawerLayout;
import android.widget.ArrayAdapter;
import android.widget.TextView;
<#if applicationPackage??>import ${applicationPackage}.R;</#if>
public class ${activityClass} extends ${(appCompat)?string('ActionBar','')}Activity
implements NavigationDrawerFragment.NavigationDrawerCallbacks {
/**
* Fragment managing the behaviors, interactions and presentation of the navigation drawer.
*/
private NavigationDrawerFragment mNavigationDrawerFragment;
/**
* Used to store the last screen title. For use in {@link #restoreActionBar()}.
*/
private CharSequence mTitle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.${layoutName});
mNavigationDrawerFragment = (NavigationDrawerFragment)
get${Support}FragmentManager().findFragmentById(R.id.navigation_drawer);
mTitle = getTitle();
// Set up the drawer.
mNavigationDrawerFragment.setUp(
R.id.navigation_drawer,
(DrawerLayout) findViewById(R.id.drawer_layout));
}
@Override
public void onNavigationDrawerItemSelected(int position) {
// update the main content by replacing fragments
FragmentManager fragmentManager = get${Support}FragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
.commit();
}
public void onSectionAttached(int number) {
switch (number) {
case 1:
mTitle = getString(R.string.title_section1);
break;
case 2:
mTitle = getString(R.string.title_section2);
break;
case 3:
mTitle = getString(R.string.title_section3);
break;
}
}
public void restoreActionBar() {
ActionBar actionBar = get${Support}ActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setTitle(mTitle);
}
<#include "include_options_menu.java.ftl">
<#include "include_fragment.java.ftl">
}

View File

@@ -0,0 +1,282 @@
package ${packageName};
<#if appCompat>import android.support.v7.app.ActionBarActivity;</#if>
import android.app.Activity;
import android.<#if appCompat>support.v7.</#if>app.ActionBar;
import android.<#if appCompat>support.v4.</#if>app.Fragment;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
/**
* Fragment used for managing interactions for and presentation of a navigation drawer.
* See the <a href="https://developer.android.com/design/patterns/navigation-drawer.html#Interaction">
* design guidelines</a> for a complete explanation of the behaviors implemented here.
*/
public class NavigationDrawerFragment extends Fragment {
/**
* Remember the position of the selected item.
*/
private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position";
/**
* Per the design guidelines, you should show the drawer on launch until the user manually
* expands it. This shared preference tracks this.
*/
private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned";
/**
* A pointer to the current callbacks instance (the Activity).
*/
private NavigationDrawerCallbacks mCallbacks;
/**
* Helper component that ties the action bar to the navigation drawer.
*/
private ActionBarDrawerToggle mDrawerToggle;
private DrawerLayout mDrawerLayout;
private ListView mDrawerListView;
private View mFragmentContainerView;
private int mCurrentSelectedPosition = 0;
private boolean mFromSavedInstanceState;
private boolean mUserLearnedDrawer;
public NavigationDrawerFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Read in the flag indicating whether or not the user has demonstrated awareness of the
// drawer. See PREF_USER_LEARNED_DRAWER for details.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
if (savedInstanceState != null) {
mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
mFromSavedInstanceState = true;
}
// Select either the default item (0) or the last selected item.
selectItem(mCurrentSelectedPosition);
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Indicate that this fragment would like to influence the set of actions in the action bar.
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mDrawerListView = (ListView) inflater.inflate(
R.layout.${navigationDrawerLayout}, container, false);
mDrawerListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
});
mDrawerListView.setAdapter(new ArrayAdapter<String>(
getActionBar().getThemedContext(),
android.R.layout.simple_list_item_<#if minApiLevel gte 11>activated_</#if>1,
android.R.id.text1,
new String[]{
getString(R.string.title_section1),
getString(R.string.title_section2),
getString(R.string.title_section3),
}));
mDrawerListView.setItemChecked(mCurrentSelectedPosition, true);
return mDrawerListView;
}
public boolean isDrawerOpen() {
return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView);
}
/**
* Users of this fragment must call this method to set up the navigation drawer interactions.
*
* @param fragmentId The android:id of this fragment in its activity's layout.
* @param drawerLayout The DrawerLayout containing this fragment's UI.
*/
public void setUp(int fragmentId, DrawerLayout drawerLayout) {
mFragmentContainerView = getActivity().findViewById(fragmentId);
mDrawerLayout = drawerLayout;
// set a custom shadow that overlays the main content when the drawer opens
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
// set up the drawer's list view with items and click listener
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
// ActionBarDrawerToggle ties together the the proper interactions
// between the navigation drawer and the action bar app icon.
mDrawerToggle = new ActionBarDrawerToggle(
getActivity(), /* host Activity */
mDrawerLayout, /* DrawerLayout object */
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
R.string.navigation_drawer_open, /* "open drawer" description for accessibility */
R.string.navigation_drawer_close /* "close drawer" description for accessibility */
) {
@Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
if (!isAdded()) {
return;
}
getActivity().${appCompat?string('supportInvalidate','invalidate')}OptionsMenu(); // calls onPrepareOptionsMenu()
}
@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
if (!isAdded()) {
return;
}
if (!mUserLearnedDrawer) {
// The user manually opened the drawer; store this flag to prevent auto-showing
// the navigation drawer automatically in the future.
mUserLearnedDrawer = true;
SharedPreferences sp = PreferenceManager
.getDefaultSharedPreferences(getActivity());
sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).${(minApiLevel gte 9)?string('apply','commit')}();
}
getActivity().${appCompat?string('supportInvalidate','invalidate')}OptionsMenu(); // calls onPrepareOptionsMenu()
}
};
// If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer,
// per the navigation drawer design guidelines.
if (!mUserLearnedDrawer && !mFromSavedInstanceState) {
mDrawerLayout.openDrawer(mFragmentContainerView);
}
// Defer code dependent on restoration of previous instance state.
mDrawerLayout.post(new Runnable() {
@Override
public void run() {
mDrawerToggle.syncState();
}
});
mDrawerLayout.setDrawerListener(mDrawerToggle);
}
private void selectItem(int position) {
mCurrentSelectedPosition = position;
if (mDrawerListView != null) {
mDrawerListView.setItemChecked(position, true);
}
if (mDrawerLayout != null) {
mDrawerLayout.closeDrawer(mFragmentContainerView);
}
if (mCallbacks != null) {
mCallbacks.onNavigationDrawerItemSelected(position);
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallbacks = (NavigationDrawerCallbacks) activity;
} catch (ClassCastException e) {
throw new ClassCastException("Activity must implement NavigationDrawerCallbacks.");
}
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Forward the new configuration the drawer toggle component.
mDrawerToggle.onConfigurationChanged(newConfig);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// If the drawer is open, show the global app actions in the action bar. See also
// showGlobalContextActionBar, which controls the top-left area of the action bar.
if (mDrawerLayout != null && isDrawerOpen()) {
inflater.inflate(R.menu.global, menu);
showGlobalContextActionBar();
}
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
if (item.getItemId() == R.id.action_example) {
Toast.makeText(getActivity(), "Example action.", Toast.LENGTH_SHORT).show();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Per the navigation drawer design guidelines, updates the action bar to show the global app
* 'context', rather than just what's in the current screen.
*/
private void showGlobalContextActionBar() {
ActionBar actionBar = getActionBar();
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
actionBar.setTitle(R.string.app_name);
}
private ActionBar getActionBar() {
return <#if appCompat>((ActionBarActivity) getActivity()).getSupportActionBar();<#else>getActivity().getActionBar();</#if>
}
/**
* Callbacks interface that all activities using this fragment must implement.
*/
public static interface NavigationDrawerCallbacks {
/**
* Called when an item in the navigation drawer is selected.
*/
void onNavigationDrawerItemSelected(int position);
}
}

View File

@@ -0,0 +1,43 @@
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
/**
* The fragment argument representing the section number for this
* fragment.
*/
private static final String ARG_SECTION_NUMBER = "section_number";
/**
* Returns a new instance of this fragment for the given section
* number.
*/
public static PlaceholderFragment newInstance(int sectionNumber) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.${fragmentLayoutName}, container, false);
<#if hasSections?has_content>
TextView textView = (TextView) rootView.findViewById(R.id.section_label);
textView.setText(Integer.toString(getArguments().getInt(ARG_SECTION_NUMBER)));
</#if>
return rootView;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
((${activityClass}) activity).onSectionAttached(
getArguments().getInt(ARG_SECTION_NUMBER));
}
}

View File

@@ -0,0 +1,25 @@
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (!mNavigationDrawerFragment.isDrawerOpen()) {
// Only show items in the action bar relevant to this screen
// if the drawer is not showing. Otherwise, let the drawer
// decide what to show in the action bar.
getMenuInflater().inflate(R.menu.${menuName}, menu);
restoreActionBar();
return true;
}
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}

View File

@@ -0,0 +1,87 @@
<?xml version="1.0"?>
<template
format="3"
revision="4"
name="Navigation Drawer Activity"
minApi="7"
minBuildApi="14"
description="Creates a new Activity with a Navigation Drawer.">
<category value="Activity" />
<formfactor value="Mobile" />
<parameter
id="activityClass"
name="Activity Name"
type="string"
constraints="class|unique|nonempty"
suggest="${layoutToActivity(layoutName)}"
default="MainActivity"
help="The name of the activity class to create" />
<parameter
id="layoutName"
name="Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="${activityToLayout(activityClass)}"
default="activity_main"
help="The name of the layout to create for the activity" />
<parameter
id="fragmentLayoutName"
name="Fragment Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="fragment_${classToResource(activityClass)}"
default="fragment_main"
help="The name of the layout to create for the activity's content fragment"/>
<parameter
id="activityTitle"
name="Title"
type="string"
constraints="nonempty"
default="MainActivity"
suggest="${activityClass}"
help="The name of the activity. For launcher activities, the application title." />
<parameter
id="isLauncher"
name="Launcher Activity"
type="boolean"
default="false"
help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
<parameter
id="parentActivityClass"
name="Hierarchical Parent"
type="string"
constraints="activity|exists|empty"
default=""
help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />
<parameter
id="packageName"
name="Package name"
type="string"
constraints="package"
default="com.mycompany.myapp" />
<parameter
id="navigationDrawerLayout"
name="Navigation Drawer Fragment Name"
type="string"
constraints="layout|unique"
default="fragment_navigation_drawer"/>
<!-- 128x128 thumbnails relative to template.xml -->
<thumbs>
<!-- default thumbnail is required -->
<thumb>template_blank_activity_drawer.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>

Some files were not shown because too many files have changed in this diff Show More