タス デザイン グループ

Read Article

フリックアクションで戻る操作やリスト要素の削除を行う

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.co.tasdg.a27_swipe_delete">

    <application android:allowBackup="true"
        android:label="@string/app_name"
        android:icon="@drawable/ic_launcher"
        android:theme="@style/AppTheme">
        <activity
            android:name=".SwipeDelete"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

layout/list_element.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/listElement"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Click Me"
    android:textSize="36sp"
    android:clickable="true"
    android:background="#3232FF" />

layout/swipe_delete.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:weightSum="1"
    android:background="#FFFFFF">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:weightSum="1"
        android:background="#8080A0">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="Tips 27: Swipe to Delete"
        android:id="@+id/mainTitle" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="This is a demonstration of swipe-to-delete functionality in a list."
        android:id="@+id/mainText" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="The List"
        android:id="@+id/listTitle" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="Add items to the list by clicking the button below. Swipe an item to the right to delete."
        android:id="@+id/listText" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Populate"
        android:id="@+id/populateButton"
        android:layout_gravity="center_horizontal"
        android:onClick="populate" />
    </LinearLayout>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:layout_gravity="center_horizontal"
        android:background="#C0C0DA"
        android:id="@+id/theList"></LinearLayout>


</LinearLayout>

transition/left_transition.xml

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

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <changeBounds android:id="@+id/leftTransition" />
</transitionSet>

SwipeDelete.java

package jp.co.tasdg.a27_swipe_delete;

/**
 * Created by Coty Saxman on 2014/08/05.
 * TAS Design Group
 * Foxroid Tips #27
 * Swipe-to-Delete
 */
import android.app.Activity;
import android.graphics.Color;
import android.graphics.Point;
import android.os.Bundle;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.View.OnTouchListener;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
 * Controls the deletion of objects on completion of a swipe to the right
 */

public class SwipeDelete extends Activity {
    ////Constants////
    /**percentage of screen traversed before erasing*/
    public static final int ERASE_BOUNDARY = 30;
    /**percentage of screen traversed before fading*/
    public static final int FADE_BOUNDARY = 5;
    /**fade-out factor*/
    public static final float FADE_SPEED = 1.5f;
    /**enables fade effect*/
    public static final boolean ENABLE_OPACITY = true;
    /**enables red delete warning*/
    public static final boolean ENABLE_RED_SHADE = true;
    /**erasable color*/
    public static final int ERASE_COLOR = Color.rgb(255, 50, 50);
    /**normal color*/
    public static final int NORMAL_COLOR = Color.rgb(50, 50, 255);

    ////List Element Variables////
    LayoutInflater mInflater;   //Generates item from XML
    LinearLayout theList;       //The layout serving as a list container
    int count = 1;              //number of current list item

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.swipe_delete);
        theList = (LinearLayout) this.findViewById(R.id.theList);
        mInflater = getLayoutInflater();
    }


    /**
     * Adds an element to the list-like layout element,
     * adds event listeners to the list item element,
     * adds erase and color-fade functionality to the event listeners.
     * @param vNULL The button calling populate, which is not used, hence the NULL in its name
     */
    public void populate(View vNULL) {
        TextView v = (TextView) mInflater.inflate(R.layout.list_element, theList, false);
        v.setText(String.valueOf(count));
        v.setBackgroundColor(NORMAL_COLOR);

        theList.addView(v);

        v.setOnTouchListener(new OnTouchListener() {
             ////Touch related variables////
             int initX;     //Initial x-coordinate of touch
             int initLeft;  //Initial left of target element

             Display display = getWindowManager().getDefaultDisplay();  //The screen
             Point size;                                                //Screen size

            /**
             * Handles touch events on list items
             * @param v The list element being touched
             * @param event The touch event
             * @return Always returns true to dispose of the touch event
             */
             @Override
             public boolean onTouch(View v, MotionEvent event) {

                 ////First-time initialization of screen size////
                 if(size == null) {
                     size = new Point();    //Initialize the Point object
                     display.getSize(size); //Initialize screen size variable
                 }

                 int eventAction = event.getAction();

                 switch (eventAction) {
                     ////Touch event start////
                     case MotionEvent.ACTION_DOWN:
                         initX = (int)event.getRawX();  //Store initial touch X
                         initLeft = v.getLeft();        //Store initial left
                         break;
                     ////Drag event////
                     case MotionEvent.ACTION_MOVE:
                         v.setLeft((int) (event.getRawX() - initX));    //Move element
                         //Apply opacity filter
                         if(ENABLE_OPACITY) {
                             int fadePoint = size.x * FADE_BOUNDARY / 100;
                             int fadeRange = size.x - fadePoint;
                             int fadeOverlap = v.getLeft() - fadePoint;
                             float newFade;
                             if(v.getLeft() <= fadePoint) newFade = 1.0f;
                             else newFade = 1.0f - (FADE_SPEED *
                                     fadeOverlap / fadeRange);
                             v.setAlpha(newFade);
                         }
                         //Shade red when deletable
                         if(ENABLE_RED_SHADE) {
                             if(canErase(v)) {
                                 v.setBackgroundColor(ERASE_COLOR);
                             }
                             else v.setBackgroundColor(NORMAL_COLOR);
                         }
                         break;
                     ////Touch event end////
                     case MotionEvent.ACTION_UP:
                         if(ENABLE_RED_SHADE) v.setBackgroundColor(NORMAL_COLOR);
                         if(canErase(v)) {
                             ((ViewGroup) v.getParent()).removeView(v); //Delete
                         }
                         else {
                             v.setLeft(initLeft);
                             if(ENABLE_OPACITY) v.setAlpha(1.0f);
                         }
                         resetVariables();
                         break;
                 }
                 return true;
             }

             ////Check if the element is in erasable position////
             private boolean canErase(View v) {
                 int erasePoint = size.x * ERASE_BOUNDARY / 100;
                 int currentX = v.getLeft();
                 return (currentX > erasePoint);
             }

             ////Reset event-specific variables////
             private void resetVariables() {
                 initX = 0;
                 initLeft = 0;
             }
        });

        count++;
    }
}

manifest.webapp

{
  "name": "swipe_delete",
  "description": "27_swipe_delete",
  "launch_path": "/index.html",
  "type": "web",
  "icons": {
    "64": "/img/icon/app-icon64.png",
    "128": "/img/icon/app-icon128.png"
  },
  "developer": {
    "name": "TAS Design Group",
    "url": "http://tasdg.co.jp/"
  },
  "locales": {
   "en": {
     "description": "Foxroid Tips #27. Slide to remove elements."
   }
  },
  "default_locale": "en"
}

CSS (style/style.css)

@charset "UTF-8";

html {
  height: 100%;
  font-size: 62.5%;
}

body {
  margin: 0;
  height: 100%;
  display: block;
  overflow: hidden;
  font-family: sans-serif;
  background-color: #C1C1D1;
}

div.list {
  height: 50%;
}

div.listItem {
  border-style: outset;
  border-width: thick;
  position: relative;
  background-color: black;
  left: 0px;
  width: 250px;
  height: 25px;
  
  transition-property: left;
  transition-timing-function: ease-out;
}

section.debug {
  position: absolute;
  bottom: 5%;
}

index.html

<!DOCTYPE html>
<html lang="ja" dir="ltr">
<head>
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
  <meta charset="utf-8">
  <title>swipe_delete</title>
  <link rel="stylesheet" href="style/style.css">
  <script src="js/main.js"></script>
</head>
<body>
  <section>
    <header>
      <h1>Tips 27: Swipe to Delete</h1>
    </header>
    <article>This is a demonstration of swipe-to-delete functionality in a list.</article>
  </section>
  <section>
    <h3>The List</h3>
    <article>Add items to the list by clicking the button below. Swipe an item to the right to delete.</article>
    <center><input type="button" value="Populate" onclick="populate('theList');"></center>
    <div class="list" id="theList">
    </div>
  </section>
  <section class="debug">
    <h3>Debug Information</h3>
    <article id="debug">Debug text goes here.</article>
  </section>
</body>
</html>

main.js

////Constants////
const eraseBoundary = 30;    //percentage of screen traversed before erasing
const fadeBoundary = 5;      //percentage of screen traversed before fading
const fadeSpeed = 1.5;       //fade-out factor
const enableOpacity = true;  //enables fade effect
const enableRedShade = true; //enables red delete warning
const snapTime = "250ms";    //animation time for snap-back

////prep debug space////
window.addEventListener('load', function(){
  debugTxt = document.getElementById("debug");
});

var debugTxt; //debugging field

////Add element to List////
var idCounter = 0; //tracks current element-to-add

function populate(target)
{
  //create the new list item
  var newDiv = document.createElement('div');                 //create the new element
  newDiv.className = 'listItem';                              //add class name
  newDiv.setAttribute('id', 'item' + idCounter);              //add numbered id
  
  //event listeners
  newDiv.addEventListener('touchstart', touchStart, false);   //touchscreen contact
  newDiv.addEventListener('touchmove', touchMove, false);     //dragging
  newDiv.addEventListener('touchend', touchEnd, false);       //touch released
  newDiv.addEventListener('touchcancel', touchCancel, false); //touch out-of-bounds

  //insert element
  document.getElementById(target).insertBefore(newDiv, null);
  
  //increment counter (prep for next run)
  idCounter++;
};

////Touch related variables////
var touchX;       //The current touched X-coordinate
var touchY;       //The current touched Y-coordinate
var initTouchX;   //The initial X-coordinate
var initTouchY;   //The initial Y-coordinate
var initLocation; //The initial location of the touched object

////The element is touched////
function touchStart(e)
{
  //Store location of initial touch
  var x = parseInt(e.changedTouches[0].clientX); //pull x from event
  var y = parseInt(e.changedTouches[0].clientY); //pull y from event
  touchX = x;
  touchY = y;
  initTouchX = x;
  initTouchY = y;
  
  initLocation = e.target.getBoundingClientRect();
  
  e.preventDefault();
};

////The element is being dragged////
function touchMove(e)
{
  //Update touch location
  touchX = parseInt(e.changedTouches[0].clientX);
  touchY = parseInt(e.changedTouches[0].clientY);
  
  //Move element horizontally
  var element = e.target;                              //The touched element
  var deltaX = touchX - initTouchX;                    //The change-in-x
  var currentX = element.getBoundingClientRect().left; //The left border of the object
  var newX = currentX + deltaX;                        //The translated left border of the object
  initTouchX = touchX;                                 //Reset initial location to prepare for next move
  element.style.left = (newX + "px");                  //Set the new location
  
  //Release transition restriction
  element.style.transitionDuration = "0s";
  
  //Apply opacity filter
  if(enableOpacity)
    {
      var screenWidth = document.body.clientWidth;                 //Get the width of the device
      var fadePoint = screenWidth * fadeBoundary / 100;            //Get the boundary point beyond which fade will start
      var fadeRange = screenWidth - fadePoint;                     //Get the available fading zone (fade point to edge)
      var fadeOverlap = newX - fadePoint;                          //Get the overlap of object with fade zone
      var newFade;                                                 //The new opacity of the object
      if(newX <= fadePoint) newFade = 1.0;                         //No fade if no overlap
      else newFade = 1.0 - (fadeSpeed * fadeOverlap / fadeRange);  //Calculate reduced opacity (factored by fadeSpeed)
      element.style.opacity = newFade;                             //Apply new opacity
      
      //Debugging information
      var testString = screenWidth + "\n" + fadePoint + "\n" + fadeRange + "\n" + fadeOverlap + "\n" + newFade + "\n" + canErase(e);
      debugTxt.innerHTML = testString; //Display debug info
    }
  //Shade red when deletable
  if(enableRedShade)
    {
      if(canErase(e))
      {
        element.style.border = "5px outset red";
      }
      else element.style.border = "";
    }
  
  e.preventDefault();
};

////Touch released within element bounds////
function touchEnd(e)
{
  var element = e.target;
  if(canErase(e)) element.parentNode.removeChild(element); //Delete
  else 
  {
    touchCancel(e); //Treat as failed touch
  }
  
  resetVariables();
};

////Touch failed (out-of-bounds, etc.)////
function touchCancel(e)
{
  var element = e.target;                                               //Store target element
  element.style.opacity = 1.0;                                          //Restore opacity
  element.style.transitionDuration = snapTime;                          //Restore (allow) slow bounce-back
  element.style.left = element.parentNode.getBoundingClientRect().left; //Restore x-position

  element.style.border = "";                     //Clear the border style (remove red)
  
  resetVariables(); //Restore variables related to drag
};

////Check if element is in erase area////
function canErase(e)
{
  var screenWidth = document.body.clientWidth;          //Get screen width
  var erasePoint = screenWidth * eraseBoundary / 100;   //Get erase boundary
  var currentX = e.target.getBoundingClientRect().left; //Get current location
  return (currentX > erasePoint);                       //Return whether or not item is erasable
}

////Clear variables used during dragging////
function resetVariables()
{
  touchX = null;
  touchY = null;
  initTouchX = null;
  initTouchY = null;
  initLocation = null;
}
Return Top