================================================
FILE: .idea/vcs.xml
================================================
================================================
FILE: README.md
================================================
# PopupList
It can show a edit menu when you press a View by a long-press gesture.
This utility class can bind a edit menu for ListView, GridView, or other View easily.
**Welcome star, fork**
## Screenshots


## How do I use PopupList?
Just need a `.java` file and a little code.
```java
PopupList popupList = new PopupList(context);
popupList.bind(anchorView, popupMenuItemList, PopupListListener);
```
## Sample
```java
PopupList popupList = new PopupList(this);
popupList.bind(lv_main, popupMenuItemList, new PopupList.PopupListListener() {
@Override
public boolean showPopupList(View adapterView, View contextView, int contextPosition) {
return true;
}
@Override
public void onPopupListClick(View contextView, int contextPosition, int position) {
Toast.makeText(MainActivity.this, contextPosition + "," + position, Toast.LENGTH_SHORT).show();
}
});
```
## Customization
You can get more information from the [Wiki](https://github.com/shangmingchao/PopupList/wiki/Configuration) page.
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Mon Mar 13 11:51:38 CST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: library/PopupList.java
================================================
package com.frank.popuplist;
import android.app.Activity;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;
import java.util.List;
/**
* This utility class can show a edit menu easily
*/
public class PopupList {
public static final int DEFAULT_NORMAL_TEXT_COLOR = Color.WHITE;
public static final int DEFAULT_PRESSED_TEXT_COLOR = Color.WHITE;
public static final float DEFAULT_TEXT_SIZE_DP = 14;
public static final float DEFAULT_TEXT_PADDING_LEFT_DP = 10.0f;
public static final float DEFAULT_TEXT_PADDING_TOP_DP = 5.0f;
public static final float DEFAULT_TEXT_PADDING_RIGHT_DP = 10.0f;
public static final float DEFAULT_TEXT_PADDING_BOTTOM_DP = 5.0f;
public static final int DEFAULT_NORMAL_BACKGROUND_COLOR = 0xCC000000;
public static final int DEFAULT_PRESSED_BACKGROUND_COLOR = 0xE7777777;
public static final int DEFAULT_BACKGROUND_RADIUS_DP = 8;
public static final int DEFAULT_DIVIDER_COLOR = 0x9AFFFFFF;
public static final float DEFAULT_DIVIDER_WIDTH_DP = 0.5f;
public static final float DEFAULT_DIVIDER_HEIGHT_DP = 16.0f;
private Context mContext;
private PopupWindow mPopupWindow;
private View mAnchorView;
private View mAdapterView;
private View mContextView;
private View mIndicatorView;
private List mPopupItemList;
private PopupListListener mPopupListListener;
private int mContextPosition;
private float mOffsetX;
private float mOffsetY;
private StateListDrawable mLeftItemBackground;
private StateListDrawable mRightItemBackground;
private StateListDrawable mCornerItemBackground;
private ColorStateList mTextColorStateList;
private GradientDrawable mCornerBackground;
private int mIndicatorWidth;
private int mIndicatorHeight;
private int mPopupWindowWidth;
private int mPopupWindowHeight;
private int mNormalTextColor;
private int mPressedTextColor;
private float mTextSize;
private int mTextPaddingLeft;
private int mTextPaddingTop;
private int mTextPaddingRight;
private int mTextPaddingBottom;
private int mNormalBackgroundColor;
private int mPressedBackgroundColor;
private int mBackgroundCornerRadius;
private int mDividerColor;
private int mDividerWidth;
private int mDividerHeight;
public PopupList(Context context) {
this.mContext = context;
this.mNormalTextColor = DEFAULT_NORMAL_TEXT_COLOR;
this.mPressedTextColor = DEFAULT_PRESSED_TEXT_COLOR;
this.mTextSize = dp2px(DEFAULT_TEXT_SIZE_DP);
this.mTextPaddingLeft = dp2px(DEFAULT_TEXT_PADDING_LEFT_DP);
this.mTextPaddingTop = dp2px(DEFAULT_TEXT_PADDING_TOP_DP);
this.mTextPaddingRight = dp2px(DEFAULT_TEXT_PADDING_RIGHT_DP);
this.mTextPaddingBottom = dp2px(DEFAULT_TEXT_PADDING_BOTTOM_DP);
this.mNormalBackgroundColor = DEFAULT_NORMAL_BACKGROUND_COLOR;
this.mPressedBackgroundColor = DEFAULT_PRESSED_BACKGROUND_COLOR;
this.mBackgroundCornerRadius = dp2px(DEFAULT_BACKGROUND_RADIUS_DP);
this.mDividerColor = DEFAULT_DIVIDER_COLOR;
this.mDividerWidth = dp2px(DEFAULT_DIVIDER_WIDTH_DP);
this.mDividerHeight = dp2px(DEFAULT_DIVIDER_HEIGHT_DP);
this.mIndicatorView = getDefaultIndicatorView(mContext);
refreshBackgroundOrRadiusStateList();
refreshTextColorStateList(mPressedTextColor, mNormalTextColor);
}
/**
* Popup a window when anchorView is clicked and held.
* That method will call {@link View#setOnTouchListener(View.OnTouchListener)} and
* {@link View#setOnLongClickListener(View.OnLongClickListener)}(or
* {@link AbsListView#setOnItemLongClickListener(AdapterView.OnItemLongClickListener)}
* if anchorView is a instance of AbsListView), so you can only use
* {@link PopupList#showPopupListWindow(View, int, float, float, List, PopupListListener)}
* if you called those method before.
*
* @param anchorView the view on which to pin the popup window
* @param popupItemList the list of the popup menu
* @param popupListListener the Listener
*/
public void bind(View anchorView, List popupItemList, PopupListListener popupListListener) {
this.mAnchorView = anchorView;
this.mPopupItemList = popupItemList;
this.mPopupListListener = popupListListener;
this.mPopupWindow = null;
mAnchorView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mOffsetX = event.getX();
mOffsetY = event.getY();
return false;
}
});
if (mAnchorView instanceof AbsListView) {
((AbsListView) mAnchorView).setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
if (mPopupListListener != null
&& !mPopupListListener.showPopupList(parent, view, position)) {
return false;
}
mAdapterView = parent;
mContextView = view;
mContextPosition = position;
showPopupListWindow(mOffsetX, mOffsetY);
return true;
}
});
} else {
mAnchorView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mPopupListListener != null
&& !mPopupListListener.showPopupList(v, v, 0)) {
return false;
}
mContextView = v;
mContextPosition = 0;
showPopupListWindow(mOffsetX, mOffsetY);
return true;
}
});
}
}
/**
* show a popup window in a bubble style.
*
* @param anchorView the view on which to pin the popup window
* @param contextPosition context position
* @param rawX the original raw X coordinate
* @param rawY the original raw Y coordinate
* @param popupItemList the list of the popup menu
* @param popupListListener the Listener
*/
public void showPopupListWindow(View anchorView, int contextPosition, float rawX, float rawY,
List popupItemList, PopupListListener popupListListener) {
mAnchorView = anchorView;
mContextPosition = contextPosition;
mPopupItemList = popupItemList;
mPopupListListener = popupListListener;
mPopupWindow = null;
mContextView = anchorView;
if (mPopupListListener != null
&& !mPopupListListener.showPopupList(mContextView, mContextView, contextPosition)) {
return;
}
int[] location = new int[2];
mAnchorView.getLocationOnScreen(location);
showPopupListWindow(rawX - location[0], rawY - location[1]);
}
private void showPopupListWindow(float offsetX, float offsetY) {
if (mContext instanceof Activity && ((Activity) mContext).isFinishing()) {
return;
}
if (mPopupWindow == null || mPopupListListener instanceof AdapterPopupListListener) {
LinearLayout contentView = new LinearLayout(mContext);
contentView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
contentView.setOrientation(LinearLayout.VERTICAL);
LinearLayout popupListContainer = new LinearLayout(mContext);
popupListContainer.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
popupListContainer.setOrientation(LinearLayout.HORIZONTAL);
popupListContainer.setBackgroundDrawable(mCornerBackground);
contentView.addView(popupListContainer);
if (mIndicatorView != null) {
LinearLayout.LayoutParams layoutParams;
if (mIndicatorView.getLayoutParams() == null) {
layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
} else {
layoutParams = (LinearLayout.LayoutParams) mIndicatorView.getLayoutParams();
}
layoutParams.gravity = Gravity.CENTER;
mIndicatorView.setLayoutParams(layoutParams);
ViewParent viewParent = mIndicatorView.getParent();
if (viewParent instanceof ViewGroup) {
((ViewGroup) viewParent).removeView(mIndicatorView);
}
contentView.addView(mIndicatorView);
}
for (int i = 0; i < mPopupItemList.size(); i++) {
TextView textView = new TextView(mContext);
textView.setTextColor(mTextColorStateList);
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
textView.setPadding(mTextPaddingLeft, mTextPaddingTop, mTextPaddingRight, mTextPaddingBottom);
textView.setClickable(true);
final int finalI = i;
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPopupListListener != null) {
mPopupListListener.onPopupListClick(mContextView, mContextPosition, finalI);
hidePopupListWindow();
}
}
});
if (mPopupListListener instanceof AdapterPopupListListener) {
AdapterPopupListListener adapterPopupListListener = (AdapterPopupListListener) mPopupListListener;
textView.setText(adapterPopupListListener.formatText(mAdapterView, mContextView, mContextPosition, i, mPopupItemList.get(i)));
} else {
textView.setText(mPopupItemList.get(i));
}
if (mPopupItemList.size() > 1 && i == 0) {
textView.setBackgroundDrawable(mLeftItemBackground);
} else if (mPopupItemList.size() > 1 && i == mPopupItemList.size() - 1) {
textView.setBackgroundDrawable(mRightItemBackground);
} else if (mPopupItemList.size() == 1) {
textView.setBackgroundDrawable(mCornerItemBackground);
} else {
textView.setBackgroundDrawable(getCenterItemBackground());
}
popupListContainer.addView(textView);
if (mPopupItemList.size() > 1 && i != mPopupItemList.size() - 1) {
View divider = new View(mContext);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(mDividerWidth, mDividerHeight);
layoutParams.gravity = Gravity.CENTER;
divider.setLayoutParams(layoutParams);
divider.setBackgroundColor(mDividerColor);
popupListContainer.addView(divider);
}
}
if (mPopupWindowWidth == 0) {
mPopupWindowWidth = getViewWidth(popupListContainer);
}
if (mIndicatorView != null && mIndicatorWidth == 0) {
if (mIndicatorView.getLayoutParams().width > 0) {
mIndicatorWidth = mIndicatorView.getLayoutParams().width;
} else {
mIndicatorWidth = getViewWidth(mIndicatorView);
}
}
if (mIndicatorView != null && mIndicatorHeight == 0) {
if (mIndicatorView.getLayoutParams().height > 0) {
mIndicatorHeight = mIndicatorView.getLayoutParams().height;
} else {
mIndicatorHeight = getViewHeight(mIndicatorView);
}
}
if (mPopupWindowHeight == 0) {
mPopupWindowHeight = getViewHeight(popupListContainer) + mIndicatorHeight;
}
mPopupWindow = new PopupWindow(contentView, mPopupWindowWidth, mPopupWindowHeight, true);
mPopupWindow.setTouchable(true);
mPopupWindow.setBackgroundDrawable(new BitmapDrawable());
}
int[] location = new int[2];
mAnchorView.getLocationOnScreen(location);
if (mIndicatorView != null) {
float leftTranslationLimit = mIndicatorWidth / 2f + mBackgroundCornerRadius - mPopupWindowWidth / 2f;
float rightTranslationLimit = mPopupWindowWidth / 2f - mIndicatorWidth / 2f - mBackgroundCornerRadius;
float maxWidth = mContext.getResources().getDisplayMetrics().widthPixels;
if (location[0] + offsetX < mPopupWindowWidth / 2f) {
mIndicatorView.setTranslationX(Math.max(location[0] + offsetX - mPopupWindowWidth / 2f, leftTranslationLimit));
} else if (location[0] + offsetX + mPopupWindowWidth / 2f > maxWidth) {
mIndicatorView.setTranslationX(Math.min(location[0] + offsetX + mPopupWindowWidth / 2f - maxWidth, rightTranslationLimit));
} else {
mIndicatorView.setTranslationX(0);
}
}
if (!mPopupWindow.isShowing()) {
int x = (int) (location[0] + offsetX - mPopupWindowWidth / 2f + 0.5f);
int y = (int) (location[1] + offsetY - mPopupWindowHeight + 0.5f);
mPopupWindow.showAtLocation(mAnchorView, Gravity.NO_GRAVITY, x, y);
}
}
private void refreshBackgroundOrRadiusStateList() {
// left
GradientDrawable leftItemPressedDrawable = new GradientDrawable();
leftItemPressedDrawable.setColor(mPressedBackgroundColor);
leftItemPressedDrawable.setCornerRadii(new float[]{
mBackgroundCornerRadius, mBackgroundCornerRadius,
0, 0,
0, 0,
mBackgroundCornerRadius, mBackgroundCornerRadius});
GradientDrawable leftItemNormalDrawable = new GradientDrawable();
leftItemNormalDrawable.setColor(Color.TRANSPARENT);
leftItemNormalDrawable.setCornerRadii(new float[]{
mBackgroundCornerRadius, mBackgroundCornerRadius,
0, 0,
0, 0,
mBackgroundCornerRadius, mBackgroundCornerRadius});
mLeftItemBackground = new StateListDrawable();
mLeftItemBackground.addState(new int[]{android.R.attr.state_pressed}, leftItemPressedDrawable);
mLeftItemBackground.addState(new int[]{}, leftItemNormalDrawable);
// right
GradientDrawable rightItemPressedDrawable = new GradientDrawable();
rightItemPressedDrawable.setColor(mPressedBackgroundColor);
rightItemPressedDrawable.setCornerRadii(new float[]{
0, 0,
mBackgroundCornerRadius, mBackgroundCornerRadius,
mBackgroundCornerRadius, mBackgroundCornerRadius,
0, 0});
GradientDrawable rightItemNormalDrawable = new GradientDrawable();
rightItemNormalDrawable.setColor(Color.TRANSPARENT);
rightItemNormalDrawable.setCornerRadii(new float[]{
0, 0,
mBackgroundCornerRadius, mBackgroundCornerRadius,
mBackgroundCornerRadius, mBackgroundCornerRadius,
0, 0});
mRightItemBackground = new StateListDrawable();
mRightItemBackground.addState(new int[]{android.R.attr.state_pressed}, rightItemPressedDrawable);
mRightItemBackground.addState(new int[]{}, rightItemNormalDrawable);
// corner
GradientDrawable cornerItemPressedDrawable = new GradientDrawable();
cornerItemPressedDrawable.setColor(mPressedBackgroundColor);
cornerItemPressedDrawable.setCornerRadius(mBackgroundCornerRadius);
GradientDrawable cornerItemNormalDrawable = new GradientDrawable();
cornerItemNormalDrawable.setColor(Color.TRANSPARENT);
cornerItemNormalDrawable.setCornerRadius(mBackgroundCornerRadius);
mCornerItemBackground = new StateListDrawable();
mCornerItemBackground.addState(new int[]{android.R.attr.state_pressed}, cornerItemPressedDrawable);
mCornerItemBackground.addState(new int[]{}, cornerItemNormalDrawable);
mCornerBackground = new GradientDrawable();
mCornerBackground.setColor(mNormalBackgroundColor);
mCornerBackground.setCornerRadius(mBackgroundCornerRadius);
}
private StateListDrawable getCenterItemBackground() {
StateListDrawable centerItemBackground = new StateListDrawable();
GradientDrawable centerItemPressedDrawable = new GradientDrawable();
centerItemPressedDrawable.setColor(mPressedBackgroundColor);
GradientDrawable centerItemNormalDrawable = new GradientDrawable();
centerItemNormalDrawable.setColor(Color.TRANSPARENT);
centerItemBackground.addState(new int[]{android.R.attr.state_pressed}, centerItemPressedDrawable);
centerItemBackground.addState(new int[]{}, centerItemNormalDrawable);
return centerItemBackground;
}
private void refreshTextColorStateList(int pressedTextColor, int normalTextColor) {
int[][] states = new int[2][];
states[0] = new int[]{android.R.attr.state_pressed};
states[1] = new int[]{};
int[] colors = new int[]{pressedTextColor, normalTextColor};
mTextColorStateList = new ColorStateList(states, colors);
}
public void hidePopupListWindow() {
if (mContext instanceof Activity && ((Activity) mContext).isFinishing()) {
return;
}
if (mPopupWindow != null && mPopupWindow.isShowing()) {
mPopupWindow.dismiss();
}
}
public View getIndicatorView() {
return mIndicatorView;
}
public View getDefaultIndicatorView(Context context) {
return getTriangleIndicatorView(context, dp2px(16), dp2px(8), DEFAULT_NORMAL_BACKGROUND_COLOR);
}
public View getTriangleIndicatorView(Context context, final float widthPixel, final float heightPixel,
final int color) {
ImageView indicator = new ImageView(context);
Drawable drawable = new Drawable() {
@Override
public void draw(Canvas canvas) {
Path path = new Path();
Paint paint = new Paint();
paint.setColor(color);
paint.setStyle(Paint.Style.FILL);
path.moveTo(0f, 0f);
path.lineTo(widthPixel, 0f);
path.lineTo(widthPixel / 2, heightPixel);
path.close();
canvas.drawPath(path, paint);
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public int getIntrinsicWidth() {
return (int) widthPixel;
}
@Override
public int getIntrinsicHeight() {
return (int) heightPixel;
}
};
indicator.setImageDrawable(drawable);
return indicator;
}
public void setIndicatorView(View indicatorView) {
this.mIndicatorView = indicatorView;
}
public void setIndicatorSize(int widthPixel, int heightPixel) {
this.mIndicatorWidth = widthPixel;
this.mIndicatorHeight = heightPixel;
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(mIndicatorWidth, mIndicatorHeight);
layoutParams.gravity = Gravity.CENTER;
if (mIndicatorView != null) {
mIndicatorView.setLayoutParams(layoutParams);
}
}
public int getNormalTextColor() {
return mNormalTextColor;
}
public void setNormalTextColor(int normalTextColor) {
this.mNormalTextColor = normalTextColor;
refreshTextColorStateList(mPressedTextColor, mNormalTextColor);
}
public int getPressedTextColor() {
return mPressedTextColor;
}
public void setPressedTextColor(int pressedTextColor) {
this.mPressedTextColor = pressedTextColor;
refreshTextColorStateList(mPressedTextColor, mNormalTextColor);
}
public float getTextSize() {
return mTextSize;
}
public void setTextSize(float textSizePixel) {
this.mTextSize = textSizePixel;
}
public int getTextPaddingLeft() {
return mTextPaddingLeft;
}
public void setTextPaddingLeft(int textPaddingLeft) {
this.mTextPaddingLeft = textPaddingLeft;
}
public int getTextPaddingTop() {
return mTextPaddingTop;
}
public void setTextPaddingTop(int textPaddingTop) {
this.mTextPaddingTop = textPaddingTop;
}
public int getTextPaddingRight() {
return mTextPaddingRight;
}
public void setTextPaddingRight(int textPaddingRight) {
this.mTextPaddingRight = textPaddingRight;
}
public int getTextPaddingBottom() {
return mTextPaddingBottom;
}
public void setTextPaddingBottom(int textPaddingBottom) {
this.mTextPaddingBottom = textPaddingBottom;
}
/**
* @param left the left padding in pixels
* @param top the top padding in pixels
* @param right the right padding in pixels
* @param bottom the bottom padding in pixels
*/
public void setTextPadding(int left, int top, int right, int bottom) {
this.mTextPaddingLeft = left;
this.mTextPaddingTop = top;
this.mTextPaddingRight = right;
this.mTextPaddingBottom = bottom;
}
public int getNormalBackgroundColor() {
return mNormalBackgroundColor;
}
public void setNormalBackgroundColor(int normalBackgroundColor) {
this.mNormalBackgroundColor = normalBackgroundColor;
refreshBackgroundOrRadiusStateList();
}
public int getPressedBackgroundColor() {
return mPressedBackgroundColor;
}
public void setPressedBackgroundColor(int pressedBackgroundColor) {
this.mPressedBackgroundColor = pressedBackgroundColor;
refreshBackgroundOrRadiusStateList();
}
public int getBackgroundCornerRadius() {
return mBackgroundCornerRadius;
}
public void setBackgroundCornerRadius(int backgroundCornerRadiusPixel) {
this.mBackgroundCornerRadius = backgroundCornerRadiusPixel;
refreshBackgroundOrRadiusStateList();
}
public int getDividerColor() {
return mDividerColor;
}
public void setDividerColor(int dividerColor) {
this.mDividerColor = dividerColor;
}
public int getDividerWidth() {
return mDividerWidth;
}
public void setDividerWidth(int dividerWidthPixel) {
this.mDividerWidth = dividerWidthPixel;
}
public int getDividerHeight() {
return mDividerHeight;
}
public void setDividerHeight(int dividerHeightPixel) {
this.mDividerHeight = dividerHeightPixel;
}
public Resources getResources() {
if (mContext == null) {
return Resources.getSystem();
} else {
return mContext.getResources();
}
}
private int getViewWidth(View view) {
view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
return view.getMeasuredWidth();
}
private int getViewHeight(View view) {
view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
return view.getMeasuredHeight();
}
public int dp2px(float value) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
value, getResources().getDisplayMetrics());
}
public int sp2px(float value) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
value, getResources().getDisplayMetrics());
}
public interface PopupListListener {
/**
* Whether the PopupList should be bound to the special view
*
* @param adapterView The context view(The AbsListView where the click happened or normal view).
* @param contextView The view within the AbsListView that was clicked or normal view
* @param contextPosition The position of the view in the list
* @return true if the view should bind the PopupList, false otherwise
*/
boolean showPopupList(View adapterView, View contextView, int contextPosition);
/**
* The callback to be invoked with an item in this PopupList has
* been clicked
*
* @param contextView The context view(The AbsListView where the click happened or normal view).
* @param contextPosition The position of the view in the list
* @param position The position of the view in the PopupList
*/
void onPopupListClick(View contextView, int contextPosition, int position);
}
public interface AdapterPopupListListener extends PopupListListener {
String formatText(View adapterView, View contextView, int contextPosition, int position, String text);
}
}
================================================
FILE: samples/.gitignore
================================================
/build
================================================
FILE: samples/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.frank.popuplist"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0-alpha3'
implementation 'com.android.support.constraint:constraint-layout:1.1.1'
testImplementation 'junit:junit:4.12'
}
================================================
FILE: samples/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Users\frank\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: samples/src/androidTest/java/com/frank/popuplist/ApplicationTest.java
================================================
package com.frank.popuplist;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: samples/src/main/AndroidManifest.xml
================================================
================================================
FILE: samples/src/main/java/com/frank/popuplist/PopupList.java
================================================
package com.frank.popuplist;
import android.app.Activity;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;
import java.util.List;
/**
* This utility class can show a edit menu easily
*/
public class PopupList {
public static final int DEFAULT_NORMAL_TEXT_COLOR = Color.WHITE;
public static final int DEFAULT_PRESSED_TEXT_COLOR = Color.WHITE;
public static final float DEFAULT_TEXT_SIZE_DP = 14;
public static final float DEFAULT_TEXT_PADDING_LEFT_DP = 10.0f;
public static final float DEFAULT_TEXT_PADDING_TOP_DP = 5.0f;
public static final float DEFAULT_TEXT_PADDING_RIGHT_DP = 10.0f;
public static final float DEFAULT_TEXT_PADDING_BOTTOM_DP = 5.0f;
public static final int DEFAULT_NORMAL_BACKGROUND_COLOR = 0xCC000000;
public static final int DEFAULT_PRESSED_BACKGROUND_COLOR = 0xE7777777;
public static final int DEFAULT_BACKGROUND_RADIUS_DP = 8;
public static final int DEFAULT_DIVIDER_COLOR = 0x9AFFFFFF;
public static final float DEFAULT_DIVIDER_WIDTH_DP = 0.5f;
public static final float DEFAULT_DIVIDER_HEIGHT_DP = 16.0f;
private Context mContext;
private PopupWindow mPopupWindow;
private View mAnchorView;
private View mAdapterView;
private View mContextView;
private View mIndicatorView;
private List mPopupItemList;
private PopupListListener mPopupListListener;
private int mContextPosition;
private float mOffsetX;
private float mOffsetY;
private StateListDrawable mLeftItemBackground;
private StateListDrawable mRightItemBackground;
private StateListDrawable mCornerItemBackground;
private ColorStateList mTextColorStateList;
private GradientDrawable mCornerBackground;
private int mIndicatorWidth;
private int mIndicatorHeight;
private int mPopupWindowWidth;
private int mPopupWindowHeight;
private int mNormalTextColor;
private int mPressedTextColor;
private float mTextSize;
private int mTextPaddingLeft;
private int mTextPaddingTop;
private int mTextPaddingRight;
private int mTextPaddingBottom;
private int mNormalBackgroundColor;
private int mPressedBackgroundColor;
private int mBackgroundCornerRadius;
private int mDividerColor;
private int mDividerWidth;
private int mDividerHeight;
public PopupList(Context context) {
this.mContext = context;
this.mNormalTextColor = DEFAULT_NORMAL_TEXT_COLOR;
this.mPressedTextColor = DEFAULT_PRESSED_TEXT_COLOR;
this.mTextSize = dp2px(DEFAULT_TEXT_SIZE_DP);
this.mTextPaddingLeft = dp2px(DEFAULT_TEXT_PADDING_LEFT_DP);
this.mTextPaddingTop = dp2px(DEFAULT_TEXT_PADDING_TOP_DP);
this.mTextPaddingRight = dp2px(DEFAULT_TEXT_PADDING_RIGHT_DP);
this.mTextPaddingBottom = dp2px(DEFAULT_TEXT_PADDING_BOTTOM_DP);
this.mNormalBackgroundColor = DEFAULT_NORMAL_BACKGROUND_COLOR;
this.mPressedBackgroundColor = DEFAULT_PRESSED_BACKGROUND_COLOR;
this.mBackgroundCornerRadius = dp2px(DEFAULT_BACKGROUND_RADIUS_DP);
this.mDividerColor = DEFAULT_DIVIDER_COLOR;
this.mDividerWidth = dp2px(DEFAULT_DIVIDER_WIDTH_DP);
this.mDividerHeight = dp2px(DEFAULT_DIVIDER_HEIGHT_DP);
this.mIndicatorView = getDefaultIndicatorView(mContext);
refreshBackgroundOrRadiusStateList();
refreshTextColorStateList(mPressedTextColor, mNormalTextColor);
}
/**
* Popup a window when anchorView is clicked and held.
* That method will call {@link View#setOnTouchListener(View.OnTouchListener)} and
* {@link View#setOnLongClickListener(View.OnLongClickListener)}(or
* {@link AbsListView#setOnItemLongClickListener(AdapterView.OnItemLongClickListener)}
* if anchorView is a instance of AbsListView), so you can only use
* {@link PopupList#showPopupListWindow(View, int, float, float, List, PopupListListener)}
* if you called those method before.
*
* @param anchorView the view on which to pin the popup window
* @param popupItemList the list of the popup menu
* @param popupListListener the Listener
*/
public void bind(View anchorView, List popupItemList, PopupListListener popupListListener) {
this.mAnchorView = anchorView;
this.mPopupItemList = popupItemList;
this.mPopupListListener = popupListListener;
this.mPopupWindow = null;
mAnchorView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mOffsetX = event.getX();
mOffsetY = event.getY();
return false;
}
});
if (mAnchorView instanceof AbsListView) {
((AbsListView) mAnchorView).setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
if (mPopupListListener != null
&& !mPopupListListener.showPopupList(parent, view, position)) {
return false;
}
mAdapterView = parent;
mContextView = view;
mContextPosition = position;
showPopupListWindow(mOffsetX, mOffsetY);
return true;
}
});
} else {
mAnchorView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mPopupListListener != null
&& !mPopupListListener.showPopupList(v, v, 0)) {
return false;
}
mContextView = v;
mContextPosition = 0;
showPopupListWindow(mOffsetX, mOffsetY);
return true;
}
});
}
}
/**
* show a popup window in a bubble style.
*
* @param anchorView the view on which to pin the popup window
* @param contextPosition context position
* @param rawX the original raw X coordinate
* @param rawY the original raw Y coordinate
* @param popupItemList the list of the popup menu
* @param popupListListener the Listener
*/
public void showPopupListWindow(View anchorView, int contextPosition, float rawX, float rawY,
List popupItemList, PopupListListener popupListListener) {
mAnchorView = anchorView;
mContextPosition = contextPosition;
mPopupItemList = popupItemList;
mPopupListListener = popupListListener;
mPopupWindow = null;
mContextView = anchorView;
if (mPopupListListener != null
&& !mPopupListListener.showPopupList(mContextView, mContextView, contextPosition)) {
return;
}
int[] location = new int[2];
mAnchorView.getLocationOnScreen(location);
showPopupListWindow(rawX - location[0], rawY - location[1]);
}
private void showPopupListWindow(float offsetX, float offsetY) {
if (mContext instanceof Activity && ((Activity) mContext).isFinishing()) {
return;
}
if (mPopupWindow == null || mPopupListListener instanceof AdapterPopupListListener) {
LinearLayout contentView = new LinearLayout(mContext);
contentView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
contentView.setOrientation(LinearLayout.VERTICAL);
LinearLayout popupListContainer = new LinearLayout(mContext);
popupListContainer.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
popupListContainer.setOrientation(LinearLayout.HORIZONTAL);
popupListContainer.setBackgroundDrawable(mCornerBackground);
contentView.addView(popupListContainer);
if (mIndicatorView != null) {
LinearLayout.LayoutParams layoutParams;
if (mIndicatorView.getLayoutParams() == null) {
layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
} else {
layoutParams = (LinearLayout.LayoutParams) mIndicatorView.getLayoutParams();
}
layoutParams.gravity = Gravity.CENTER;
mIndicatorView.setLayoutParams(layoutParams);
ViewParent viewParent = mIndicatorView.getParent();
if (viewParent instanceof ViewGroup) {
((ViewGroup) viewParent).removeView(mIndicatorView);
}
contentView.addView(mIndicatorView);
}
for (int i = 0; i < mPopupItemList.size(); i++) {
TextView textView = new TextView(mContext);
textView.setTextColor(mTextColorStateList);
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
textView.setPadding(mTextPaddingLeft, mTextPaddingTop, mTextPaddingRight, mTextPaddingBottom);
textView.setClickable(true);
final int finalI = i;
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPopupListListener != null) {
mPopupListListener.onPopupListClick(mContextView, mContextPosition, finalI);
hidePopupListWindow();
}
}
});
if (mPopupListListener instanceof AdapterPopupListListener) {
AdapterPopupListListener adapterPopupListListener = (AdapterPopupListListener) mPopupListListener;
textView.setText(adapterPopupListListener.formatText(mAdapterView, mContextView, mContextPosition, i, mPopupItemList.get(i)));
} else {
textView.setText(mPopupItemList.get(i));
}
if (mPopupItemList.size() > 1 && i == 0) {
textView.setBackgroundDrawable(mLeftItemBackground);
} else if (mPopupItemList.size() > 1 && i == mPopupItemList.size() - 1) {
textView.setBackgroundDrawable(mRightItemBackground);
} else if (mPopupItemList.size() == 1) {
textView.setBackgroundDrawable(mCornerItemBackground);
} else {
textView.setBackgroundDrawable(getCenterItemBackground());
}
popupListContainer.addView(textView);
if (mPopupItemList.size() > 1 && i != mPopupItemList.size() - 1) {
View divider = new View(mContext);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(mDividerWidth, mDividerHeight);
layoutParams.gravity = Gravity.CENTER;
divider.setLayoutParams(layoutParams);
divider.setBackgroundColor(mDividerColor);
popupListContainer.addView(divider);
}
}
if (mPopupWindowWidth == 0) {
mPopupWindowWidth = getViewWidth(popupListContainer);
}
if (mIndicatorView != null && mIndicatorWidth == 0) {
if (mIndicatorView.getLayoutParams().width > 0) {
mIndicatorWidth = mIndicatorView.getLayoutParams().width;
} else {
mIndicatorWidth = getViewWidth(mIndicatorView);
}
}
if (mIndicatorView != null && mIndicatorHeight == 0) {
if (mIndicatorView.getLayoutParams().height > 0) {
mIndicatorHeight = mIndicatorView.getLayoutParams().height;
} else {
mIndicatorHeight = getViewHeight(mIndicatorView);
}
}
if (mPopupWindowHeight == 0) {
mPopupWindowHeight = getViewHeight(popupListContainer) + mIndicatorHeight;
}
mPopupWindow = new PopupWindow(contentView, mPopupWindowWidth, mPopupWindowHeight, true);
mPopupWindow.setTouchable(true);
mPopupWindow.setBackgroundDrawable(new BitmapDrawable());
}
int[] location = new int[2];
mAnchorView.getLocationOnScreen(location);
if (mIndicatorView != null) {
float leftTranslationLimit = mIndicatorWidth / 2f + mBackgroundCornerRadius - mPopupWindowWidth / 2f;
float rightTranslationLimit = mPopupWindowWidth / 2f - mIndicatorWidth / 2f - mBackgroundCornerRadius;
float maxWidth = mContext.getResources().getDisplayMetrics().widthPixels;
if (location[0] + offsetX < mPopupWindowWidth / 2f) {
mIndicatorView.setTranslationX(Math.max(location[0] + offsetX - mPopupWindowWidth / 2f, leftTranslationLimit));
} else if (location[0] + offsetX + mPopupWindowWidth / 2f > maxWidth) {
mIndicatorView.setTranslationX(Math.min(location[0] + offsetX + mPopupWindowWidth / 2f - maxWidth, rightTranslationLimit));
} else {
mIndicatorView.setTranslationX(0);
}
}
if (!mPopupWindow.isShowing()) {
int x = (int) (location[0] + offsetX - mPopupWindowWidth / 2f + 0.5f);
int y = (int) (location[1] + offsetY - mPopupWindowHeight + 0.5f);
mPopupWindow.showAtLocation(mAnchorView, Gravity.NO_GRAVITY, x, y);
}
}
private void refreshBackgroundOrRadiusStateList() {
// left
GradientDrawable leftItemPressedDrawable = new GradientDrawable();
leftItemPressedDrawable.setColor(mPressedBackgroundColor);
leftItemPressedDrawable.setCornerRadii(new float[]{
mBackgroundCornerRadius, mBackgroundCornerRadius,
0, 0,
0, 0,
mBackgroundCornerRadius, mBackgroundCornerRadius});
GradientDrawable leftItemNormalDrawable = new GradientDrawable();
leftItemNormalDrawable.setColor(Color.TRANSPARENT);
leftItemNormalDrawable.setCornerRadii(new float[]{
mBackgroundCornerRadius, mBackgroundCornerRadius,
0, 0,
0, 0,
mBackgroundCornerRadius, mBackgroundCornerRadius});
mLeftItemBackground = new StateListDrawable();
mLeftItemBackground.addState(new int[]{android.R.attr.state_pressed}, leftItemPressedDrawable);
mLeftItemBackground.addState(new int[]{}, leftItemNormalDrawable);
// right
GradientDrawable rightItemPressedDrawable = new GradientDrawable();
rightItemPressedDrawable.setColor(mPressedBackgroundColor);
rightItemPressedDrawable.setCornerRadii(new float[]{
0, 0,
mBackgroundCornerRadius, mBackgroundCornerRadius,
mBackgroundCornerRadius, mBackgroundCornerRadius,
0, 0});
GradientDrawable rightItemNormalDrawable = new GradientDrawable();
rightItemNormalDrawable.setColor(Color.TRANSPARENT);
rightItemNormalDrawable.setCornerRadii(new float[]{
0, 0,
mBackgroundCornerRadius, mBackgroundCornerRadius,
mBackgroundCornerRadius, mBackgroundCornerRadius,
0, 0});
mRightItemBackground = new StateListDrawable();
mRightItemBackground.addState(new int[]{android.R.attr.state_pressed}, rightItemPressedDrawable);
mRightItemBackground.addState(new int[]{}, rightItemNormalDrawable);
// corner
GradientDrawable cornerItemPressedDrawable = new GradientDrawable();
cornerItemPressedDrawable.setColor(mPressedBackgroundColor);
cornerItemPressedDrawable.setCornerRadius(mBackgroundCornerRadius);
GradientDrawable cornerItemNormalDrawable = new GradientDrawable();
cornerItemNormalDrawable.setColor(Color.TRANSPARENT);
cornerItemNormalDrawable.setCornerRadius(mBackgroundCornerRadius);
mCornerItemBackground = new StateListDrawable();
mCornerItemBackground.addState(new int[]{android.R.attr.state_pressed}, cornerItemPressedDrawable);
mCornerItemBackground.addState(new int[]{}, cornerItemNormalDrawable);
mCornerBackground = new GradientDrawable();
mCornerBackground.setColor(mNormalBackgroundColor);
mCornerBackground.setCornerRadius(mBackgroundCornerRadius);
}
private StateListDrawable getCenterItemBackground() {
StateListDrawable centerItemBackground = new StateListDrawable();
GradientDrawable centerItemPressedDrawable = new GradientDrawable();
centerItemPressedDrawable.setColor(mPressedBackgroundColor);
GradientDrawable centerItemNormalDrawable = new GradientDrawable();
centerItemNormalDrawable.setColor(Color.TRANSPARENT);
centerItemBackground.addState(new int[]{android.R.attr.state_pressed}, centerItemPressedDrawable);
centerItemBackground.addState(new int[]{}, centerItemNormalDrawable);
return centerItemBackground;
}
private void refreshTextColorStateList(int pressedTextColor, int normalTextColor) {
int[][] states = new int[2][];
states[0] = new int[]{android.R.attr.state_pressed};
states[1] = new int[]{};
int[] colors = new int[]{pressedTextColor, normalTextColor};
mTextColorStateList = new ColorStateList(states, colors);
}
public void hidePopupListWindow() {
if (mContext instanceof Activity && ((Activity) mContext).isFinishing()) {
return;
}
if (mPopupWindow != null && mPopupWindow.isShowing()) {
mPopupWindow.dismiss();
}
}
public View getIndicatorView() {
return mIndicatorView;
}
public View getDefaultIndicatorView(Context context) {
return getTriangleIndicatorView(context, dp2px(16), dp2px(8), DEFAULT_NORMAL_BACKGROUND_COLOR);
}
public View getTriangleIndicatorView(Context context, final float widthPixel, final float heightPixel,
final int color) {
ImageView indicator = new ImageView(context);
Drawable drawable = new Drawable() {
@Override
public void draw(Canvas canvas) {
Path path = new Path();
Paint paint = new Paint();
paint.setColor(color);
paint.setStyle(Paint.Style.FILL);
path.moveTo(0f, 0f);
path.lineTo(widthPixel, 0f);
path.lineTo(widthPixel / 2, heightPixel);
path.close();
canvas.drawPath(path, paint);
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public int getIntrinsicWidth() {
return (int) widthPixel;
}
@Override
public int getIntrinsicHeight() {
return (int) heightPixel;
}
};
indicator.setImageDrawable(drawable);
return indicator;
}
public void setIndicatorView(View indicatorView) {
this.mIndicatorView = indicatorView;
}
public void setIndicatorSize(int widthPixel, int heightPixel) {
this.mIndicatorWidth = widthPixel;
this.mIndicatorHeight = heightPixel;
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(mIndicatorWidth, mIndicatorHeight);
layoutParams.gravity = Gravity.CENTER;
if (mIndicatorView != null) {
mIndicatorView.setLayoutParams(layoutParams);
}
}
public int getNormalTextColor() {
return mNormalTextColor;
}
public void setNormalTextColor(int normalTextColor) {
this.mNormalTextColor = normalTextColor;
refreshTextColorStateList(mPressedTextColor, mNormalTextColor);
}
public int getPressedTextColor() {
return mPressedTextColor;
}
public void setPressedTextColor(int pressedTextColor) {
this.mPressedTextColor = pressedTextColor;
refreshTextColorStateList(mPressedTextColor, mNormalTextColor);
}
public float getTextSize() {
return mTextSize;
}
public void setTextSize(float textSizePixel) {
this.mTextSize = textSizePixel;
}
public int getTextPaddingLeft() {
return mTextPaddingLeft;
}
public void setTextPaddingLeft(int textPaddingLeft) {
this.mTextPaddingLeft = textPaddingLeft;
}
public int getTextPaddingTop() {
return mTextPaddingTop;
}
public void setTextPaddingTop(int textPaddingTop) {
this.mTextPaddingTop = textPaddingTop;
}
public int getTextPaddingRight() {
return mTextPaddingRight;
}
public void setTextPaddingRight(int textPaddingRight) {
this.mTextPaddingRight = textPaddingRight;
}
public int getTextPaddingBottom() {
return mTextPaddingBottom;
}
public void setTextPaddingBottom(int textPaddingBottom) {
this.mTextPaddingBottom = textPaddingBottom;
}
/**
* @param left the left padding in pixels
* @param top the top padding in pixels
* @param right the right padding in pixels
* @param bottom the bottom padding in pixels
*/
public void setTextPadding(int left, int top, int right, int bottom) {
this.mTextPaddingLeft = left;
this.mTextPaddingTop = top;
this.mTextPaddingRight = right;
this.mTextPaddingBottom = bottom;
}
public int getNormalBackgroundColor() {
return mNormalBackgroundColor;
}
public void setNormalBackgroundColor(int normalBackgroundColor) {
this.mNormalBackgroundColor = normalBackgroundColor;
refreshBackgroundOrRadiusStateList();
}
public int getPressedBackgroundColor() {
return mPressedBackgroundColor;
}
public void setPressedBackgroundColor(int pressedBackgroundColor) {
this.mPressedBackgroundColor = pressedBackgroundColor;
refreshBackgroundOrRadiusStateList();
}
public int getBackgroundCornerRadius() {
return mBackgroundCornerRadius;
}
public void setBackgroundCornerRadius(int backgroundCornerRadiusPixel) {
this.mBackgroundCornerRadius = backgroundCornerRadiusPixel;
refreshBackgroundOrRadiusStateList();
}
public int getDividerColor() {
return mDividerColor;
}
public void setDividerColor(int dividerColor) {
this.mDividerColor = dividerColor;
}
public int getDividerWidth() {
return mDividerWidth;
}
public void setDividerWidth(int dividerWidthPixel) {
this.mDividerWidth = dividerWidthPixel;
}
public int getDividerHeight() {
return mDividerHeight;
}
public void setDividerHeight(int dividerHeightPixel) {
this.mDividerHeight = dividerHeightPixel;
}
public Resources getResources() {
if (mContext == null) {
return Resources.getSystem();
} else {
return mContext.getResources();
}
}
private int getViewWidth(View view) {
view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
return view.getMeasuredWidth();
}
private int getViewHeight(View view) {
view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
return view.getMeasuredHeight();
}
public int dp2px(float value) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
value, getResources().getDisplayMetrics());
}
public int sp2px(float value) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
value, getResources().getDisplayMetrics());
}
public interface PopupListListener {
/**
* Whether the PopupList should be bound to the special view
*
* @param adapterView The context view(The AbsListView where the click happened or normal view).
* @param contextView The view within the AbsListView that was clicked or normal view
* @param contextPosition The position of the view in the list
* @return true if the view should bind the PopupList, false otherwise
*/
boolean showPopupList(View adapterView, View contextView, int contextPosition);
/**
* The callback to be invoked with an item in this PopupList has
* been clicked
*
* @param contextView The context view(The AbsListView where the click happened or normal view).
* @param contextPosition The position of the view in the list
* @param position The position of the view in the PopupList
*/
void onPopupListClick(View contextView, int contextPosition, int position);
}
public interface AdapterPopupListListener extends PopupListListener {
String formatText(View adapterView, View contextView, int contextPosition, int position, String text);
}
}
================================================
FILE: samples/src/main/java/com/frank/sample/MainActivity.java
================================================
package com.frank.sample;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import com.frank.popuplist.PopupList;
import com.frank.popuplist.R;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private Button btn_long_click;
private ListView lv_main;
private List mDataList = new ArrayList<>();
private ArrayAdapter mDataAdapter;
private List popupMenuItemList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_long_click = (Button) findViewById(R.id.btn_long_click);
lv_main = (ListView) findViewById(R.id.lv_main);
mDataAdapter = new ArrayAdapter<>(this, android.R.layout.simple_expandable_list_item_1, mDataList);
lv_main.setAdapter(mDataAdapter);
popupMenuItemList.add(getString(R.string.copy));
popupMenuItemList.add(getString(R.string.delete));
popupMenuItemList.add(getString(R.string.share));
popupMenuItemList.add(getString(R.string.more));
PopupList popupList = new PopupList(this);
popupList.bind(lv_main, popupMenuItemList, new PopupList.PopupListListener() {
@Override
public boolean showPopupList(View adapterView, View contextView, int contextPosition) {
return true;
}
@Override
public void onPopupListClick(View contextView, int contextPosition, int position) {
Toast.makeText(MainActivity.this, contextPosition + "," + position, Toast.LENGTH_SHORT).show();
}
});
PopupList normalViewPopupList = new PopupList(this);
normalViewPopupList.bind(btn_long_click, popupMenuItemList, new PopupList.PopupListListener() {
@Override
public boolean showPopupList(View adapterView, View contextView, int contextPosition) {
return true;
}
@Override
public void onPopupListClick(View contextView, int contextPosition, int position) {
Toast.makeText(MainActivity.this, contextPosition + "," + position, Toast.LENGTH_SHORT).show();
}
});
lv_main.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this, "onItemClicked:" + position, Toast.LENGTH_SHORT).show();
}
});
btn_long_click.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, SecondaryActivity.class));
}
});
getData();
}
private void getData() {
for (int i = 0; i < 40; i++) {
mDataList.add("No." + i);
}
mDataAdapter.notifyDataSetChanged();
}
}
================================================
FILE: samples/src/main/java/com/frank/sample/SecondaryActivity.java
================================================
package com.frank.sample;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import com.frank.popuplist.PopupList;
import com.frank.popuplist.R;
import java.util.ArrayList;
import java.util.List;
public class SecondaryActivity extends AppCompatActivity {
private ListView lv_main;
private List mDataList = new ArrayList<>();
private ArrayAdapter mDataAdapter;
private List popupMenuItemList = new ArrayList<>();
private float mRawX;
private float mRawY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_secondary);
lv_main = (ListView) findViewById(R.id.lv_main);
mDataAdapter = new ArrayAdapter<>(this, android.R.layout.simple_expandable_list_item_1, mDataList);
lv_main.setAdapter(mDataAdapter);
popupMenuItemList.add(getString(R.string.copy));
popupMenuItemList.add(getString(R.string.delete));
popupMenuItemList.add(getString(R.string.share));
popupMenuItemList.add(getString(R.string.more));
lv_main.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mRawX = event.getRawX();
mRawY = event.getRawY();
return false;
}
});
lv_main.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
Toast.makeText(SecondaryActivity.this, "onItemClicked:" + position, Toast.LENGTH_SHORT).show();
}
});
lv_main.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
int[] location = new int[2];
view.getLocationOnScreen(location);
PopupList popupList = new PopupList(view.getContext());
popupList.showPopupListWindow(view, position, location[0] + view.getWidth() / 2,
location[1], popupMenuItemList, new PopupList.PopupListListener() {
@Override
public boolean showPopupList(View adapterView, View contextView, int contextPosition) {
return true;
}
@Override
public void onPopupListClick(View contextView, int contextPosition, int position) {
Toast.makeText(contextView.getContext(), contextPosition + "," + position, Toast.LENGTH_SHORT).show();
}
});
return true;
}
});
getData();
}
private void getData() {
for (int i = 0; i < 40; i++) {
mDataList.add("No." + i);
}
mDataAdapter.notifyDataSetChanged();
}
}
================================================
FILE: samples/src/main/res/layout/activity_main.xml
================================================
================================================
FILE: samples/src/main/res/layout/activity_secondary.xml
================================================
================================================
FILE: samples/src/main/res/values/colors.xml
================================================
#3F51B5#303F9F#FF4081
================================================
FILE: samples/src/main/res/values/dimens.xml
================================================
16dp16dp
================================================
FILE: samples/src/main/res/values/strings.xml
================================================
PopupList复制删除分享更多…
================================================
FILE: samples/src/main/res/values/styles.xml
================================================
================================================
FILE: samples/src/main/res/values-en/strings.xml
================================================
PopupListCopyDeleteShareMore…
================================================
FILE: samples/src/main/res/values-w820dp/dimens.xml
================================================
64dp
================================================
FILE: samples/src/test/java/com/frank/popuplist/ExampleUnitTest.java
================================================
package com.frank.popuplist;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* To work on unit tests, switch the Test Artifact in the Build Variants view.
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
================================================
FILE: settings.gradle
================================================
include ':samples'