├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── tomergoldst │ │ └── hoverviewdemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── tomergoldst │ │ │ └── hoverviewdemo │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable │ │ └── box_shape_drawable.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── hover_view.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── tomergoldst │ └── hoverviewdemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hoverview ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── tomergoldst │ │ └── hoverview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── tomergoldst │ │ │ └── hoverview │ │ │ ├── Coordinates.java │ │ │ ├── DefaultHoverViewAnimator.java │ │ │ ├── HoverView.java │ │ │ ├── HoverViewAnimator.java │ │ │ ├── HoverViewManager.java │ │ │ ├── UiUtils.java │ │ │ └── ViewCoordinatesFinder.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── tomergoldst │ └── hoverview │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hoverview 2 | Add popup view near any other view with ease 3 | 4 | 5 | 6 | ## Instructions 7 | 8 | 1. Add a dependency to your app build.gradle 9 | ```groovy 10 | dependencies { 11 | compile 'com.tomergoldst.android:hoverview:1.0.2' 12 | } 13 | ``` 14 | 15 | 2. Create an HoverViewManager object 16 | ```java 17 | HoverViewManager mHoverViewManager; 18 | mHoverViewManager = new HoverViewManager(); 19 | ``` 20 | 21 | 3. Create your custom view to show. In following example we have created a TextView layout named `hover_view.xml` and customized it like this: 22 | ```xml 23 | 24 | 33 | 34 | ``` 35 | Important: Do not use layout_margin 36 | 37 | 4. Use the HoverView Builder to construct your hoverview 38 | ```java 39 | View view = LayoutInflater.from(this).inflate(R.layout.hover_view, mRootLayout, false); 40 | HoverView.Builder builder = new HoverView.Builder(this, mTextView, mRootLayout, view, HoverView.POSITION_ABOVE); 41 | ``` 42 | 'mTextView' here is the view which near it the hover view will be shown and 'mRootLayout' is the layout where the hoverview will be added to. 43 | The root layout must be of RelativeLayout, FrameLayout or similar. LinearLayout won't work but you can always wrap your LinearLayout 44 | with another layout. Prefer to pass in a layout which is higher in the xml tree as this will give the 45 | hoverview more visible space. 46 | 47 | 5. Use HoverViewManager to show the hoverview 48 | 49 | IMPORTANT: This must be called after the layout has been drawn 50 | You can override the 'onWindowFocusChanged()' of an Activity and show there, Start a delayed runnable from onStart() , React to user action or any other method that works for you 51 | ```java 52 | mHoverViewManager.show(builder.build()); 53 | ``` 54 | 55 | Each hoverview is dismissable by clicking on it, if you want to dismiss an hoverview from code there are a few options, The most simple way is to do the following 56 | ```java 57 | mHoverViewManager.findAndDismiss(mTextView); 58 | ``` 59 | Where 'mTextView' is the same view we asked to position an hoverview near it 60 | 61 | If you want to react when hoverview has been dismissed, Implement HoverViewManager.HoverViewListener interface and use appropriate HoverViewManager constructor 62 | ```java 63 | public class MainActivity extends AppCompatActivity implements HoverViewManager.HoverViewListener 64 | . 65 | . 66 | @Override 67 | protected void onCreate(Bundle savedInstanceState) { 68 | mHoverViewManager = new HoverViewManager(this); 69 | } 70 | . 71 | . 72 | @Override 73 | public void onHoverViewDismissed(View view, int anchorViewId, boolean byUser) { 74 | Log.d(TAG, "hoverview near anchor view " + anchorViewId + " dismissed"); 75 | 76 | if (anchorViewId == R.id.text_view) { 77 | // Do something when an hoverview near view with id "R.id.text_view" has been dismissed 78 | } 79 | } 80 | ``` 81 | 82 | ### License 83 | ``` 84 | Copyright 2016 Tomer Goldstein 85 | 86 | Licensed under the Apache License, Version 2.0 (the "License"); 87 | you may not use this file except in compliance with the License. 88 | You may obtain a copy of the License at 89 | 90 | http://www.apache.org/licenses/LICENSE-2.0 91 | 92 | Unless required by applicable law or agreed to in writing, software 93 | distributed under the License is distributed on an "AS IS" BASIS, 94 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 95 | See the License for the specific language governing permissions and 96 | limitations under the License. 97 | ``` 98 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | defaultConfig { 7 | applicationId "com.tomergoldst.hoverviewdemo" 8 | minSdkVersion 14 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { 26 | exclude group: 'com.android.support', module: 'support-annotations' 27 | }) 28 | implementation 'com.android.support:appcompat-v7:28.0.0' 29 | testImplementation 'junit:junit:4.12' 30 | implementation project(path: ':hoverview') 31 | } 32 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\Tomer\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tomergoldst/hoverviewdemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.tomergoldst.hoverviewdemo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.tomergoldst.hoverviewdemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/tomergoldst/hoverviewdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.tomergoldst.hoverviewdemo; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.util.Log; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.RadioButton; 10 | import android.widget.RelativeLayout; 11 | import android.widget.TextView; 12 | 13 | import com.tomergoldst.hoverview.HoverView; 14 | import com.tomergoldst.hoverview.HoverViewManager; 15 | 16 | public class MainActivity extends AppCompatActivity implements 17 | HoverViewManager.HoverViewListener, 18 | View.OnClickListener 19 | { 20 | private static final String TAG = MainActivity.class.getSimpleName(); 21 | 22 | HoverViewManager mHoverViewManager; 23 | RelativeLayout mRootLayout; 24 | RelativeLayout mParentLayout; 25 | TextView mTextView; 26 | 27 | Button mAboveBtn; 28 | Button mBelowBtn; 29 | Button mLeftToBtn; 30 | Button mRightToBtn; 31 | 32 | RadioButton mAlignRight; 33 | RadioButton mAlignLeft; 34 | RadioButton mAlignCenter; 35 | 36 | @HoverView.Align int mAlign = HoverView.ALIGN_CENTER; 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_main); 42 | 43 | mRootLayout = findViewById(R.id.root_layout); 44 | mParentLayout = findViewById(R.id.parent_layout); 45 | mTextView = findViewById(R.id.text_view); 46 | 47 | mHoverViewManager = new HoverViewManager(this); 48 | 49 | mAboveBtn = findViewById(R.id.button_above); 50 | mBelowBtn = findViewById(R.id.button_below); 51 | mLeftToBtn = findViewById(R.id.button_left_to); 52 | mRightToBtn = findViewById(R.id.button_right_to); 53 | 54 | mAboveBtn.setOnClickListener(this); 55 | mBelowBtn.setOnClickListener(this); 56 | mLeftToBtn.setOnClickListener(this); 57 | mRightToBtn.setOnClickListener(this); 58 | 59 | mAlignCenter = findViewById(R.id.button_align_center); 60 | mAlignLeft = findViewById(R.id.button_align_left); 61 | mAlignRight = findViewById(R.id.button_align_right); 62 | 63 | mAlignCenter.setOnClickListener(this); 64 | mAlignLeft.setOnClickListener(this); 65 | mAlignRight.setOnClickListener(this); 66 | 67 | mAlignCenter.setChecked(true); 68 | 69 | } 70 | 71 | @Override 72 | public void onWindowFocusChanged(boolean hasFocus) { 73 | super.onWindowFocusChanged(hasFocus); 74 | 75 | View view = LayoutInflater.from(this).inflate(R.layout.hover_view, mRootLayout, false); 76 | 77 | HoverView.Builder builder = new HoverView.Builder(this, 78 | mTextView, 79 | mRootLayout, 80 | view, 81 | HoverView.POSITION_ABOVE); 82 | builder.setAlign(mAlign); 83 | mHoverViewManager.show(builder.build()); 84 | } 85 | 86 | 87 | 88 | @Override 89 | public void onHoverViewDismissed(View view, int anchorViewId, boolean byUser) { 90 | Log.d(TAG, "hoverview near anchor view " + anchorViewId + " dismissed"); 91 | 92 | if (anchorViewId == R.id.text_view) { 93 | // Do something when an hoverview near view with id "R.id.text_view" has been dismissed 94 | } 95 | } 96 | 97 | @Override 98 | public void onClick(View view) { 99 | HoverView.Builder builder; 100 | 101 | View hoverView = LayoutInflater.from(this).inflate(R.layout.hover_view, mRootLayout, false); 102 | 103 | switch (view.getId()){ 104 | case R.id.button_above: 105 | mHoverViewManager.findAndDismiss(mTextView); 106 | builder = new HoverView.Builder(this, mTextView, mRootLayout, hoverView, HoverView.POSITION_ABOVE); 107 | builder.setAlign(mAlign); 108 | mHoverViewManager.show(builder.build()); 109 | break; 110 | case R.id.button_below: 111 | mHoverViewManager.findAndDismiss(mTextView); 112 | builder = new HoverView.Builder(this, mTextView, mRootLayout, hoverView, HoverView.POSITION_BELOW); 113 | builder.setAlign(mAlign); 114 | mHoverViewManager.show(builder.build()); 115 | break; 116 | case R.id.button_left_to: 117 | mHoverViewManager.findAndDismiss(mTextView); 118 | builder = new HoverView.Builder(this, mTextView, mRootLayout, hoverView, HoverView.POSITION_LEFT_TO); 119 | mHoverViewManager.show(builder.build()); 120 | break; 121 | case R.id.button_right_to: 122 | mHoverViewManager.findAndDismiss(mTextView); 123 | builder = new HoverView.Builder(this, mTextView, mRootLayout, hoverView, HoverView.POSITION_RIGHT_TO); 124 | mHoverViewManager.show(builder.build()); 125 | break; 126 | case R.id.button_align_center: 127 | mAlign = HoverView.ALIGN_CENTER; 128 | break; 129 | case R.id.button_align_left: 130 | mAlign = HoverView.ALIGN_LEFT; 131 | break; 132 | case R.id.button_align_right: 133 | mAlign = HoverView.ALIGN_RIGHT; 134 | break; 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/box_shape_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 22 | 23 | 29 | 30 |