├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── gjiazhe
│ │ └── wavesidebar
│ │ └── sample
│ │ ├── Contact.java
│ │ ├── ContactsAdapter.java
│ │ ├── CustomIndexActivity.java
│ │ ├── LeftPositionActivity.java
│ │ ├── MainActivity.java
│ │ └── RightPositionActivity.java
│ └── res
│ ├── drawable
│ └── ic_avatar.png
│ ├── layout
│ ├── activity_contacts.xml
│ ├── activity_main.xml
│ ├── item_contacts.xml
│ └── item_contacts_right.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshot
├── gif.gif
├── japanese1.png
├── japanese2.png
├── lazy_respond.gif
└── position_left.png
├── settings.gradle
└── wavesidebar
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── gjiazhe
│ └── wavesidebar
│ └── WaveSideBar.java
└── res
└── values
├── attrs.xml
└── strings.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # Intellij
36 | *.iml
37 | .idea/*
38 |
39 | # Keystore files
40 | *.jks
41 |
42 | # Mac OS
43 | .DS_Store
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 郭佳哲
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WaveSideBar
2 |
3 | You can use WaveSideBar in the contacts page of your application.
4 | Refer to [AlexLiuSheng/AnimSideBar](https://github.com/AlexLiuSheng/AnimSideBar).
5 |
6 | ## Screenshot
7 | 
8 |
9 | ## Include the WaveSideBar to Your Project
10 | With gradle:
11 |
12 | ```groovy
13 | dependencies {
14 | compile 'com.gjiazhe:wavesidebar:1.3'
15 | }
16 | ```
17 |
18 | ## Use WaveSideBar in Layout File
19 | ```xml
20 |
31 | ```
32 |
33 | ## Description of Attributes
34 | | Attributes | Format | Default | Description |
35 | | :--------------------: | :------------------------: | :--------: | :--------------------------------------: |
36 | | sidebar_text_color | color | Color.GRAY | Text color of side bar. |
37 | | sidebar_text_size | dimension | 14sp | Text size of side bar. |
38 | | sidebar_max_offset | dimension | 80dp | Offset of the selected item. |
39 | | sidebar_position | enum {right, left} | right | Be placed on left or right in the view. |
40 | | sidebar_text_alignment | enum {center, left, right} | center | Alignment of items. |
41 | | sidebar_lazy_respond | boolean | false | If __true__, the listener will not be called until the finger __up__. If __false__, the listener will be called when the finger __down__, __move__ and __up__. |
42 |
43 | You can set these attributes in the layout file, or in the java code:
44 | ```java
45 | WaveSideBar sideBar = (WaveSideBar) findViewById(R.id.side_bar);
46 | sideBar.setTextColor(Color.BLACK);
47 | sideBar.setMaxOffset(100);
48 | sideBar.setPosition(WaveSideBar.POSITION_LEFT);
49 | sideBar.setTextAlign(WaveSideBar.TEXT_ALIGN_CENTER);
50 | sideBar.setLazyRespond(true);
51 | ```
52 |
53 | ## Set the Listener to Observe WaveSideBar
54 | ```java
55 | WaveSideBar sideBar = (WaveSideBar) findViewById(R.id.side_bar);
56 | sideBar.setOnSelectIndexItemListener(new WaveSideBar.OnSelectIndexItemListener() {
57 | @Override
58 | public void onSelectIndexItem(String index) {
59 | Log.d("WaveSideBar", index);
60 | // Do something here ....
61 | }
62 | });
63 | ```
64 |
65 | ## Customize the indexes
66 | Use **setIndexItems** to Customize the indexes.
67 | ```java
68 | sideBar.setIndexItems("あ", "か", "さ", "た", "な", "は", "ま", "や", "ら", "わ");
69 | ```
70 |
71 |
72 |
73 |
74 | ## Use Left Hand?
75 | Use **setPosition** to change the position of side bar.
76 | ```java
77 | sideBar.setPosition(WaveSideBar.POSITION_LEFT);
78 | ```
79 |
80 |
81 |
82 | ## Lazy respond
83 | use **setLazyRespond** to set whether the side bar should respond lazily to your touch events.
84 | ```java
85 | sideBar.setLazyRespond(true);
86 | ```
87 | 
88 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.2"
6 |
7 | defaultConfig {
8 | applicationId "com.gjiazhe.wavesidebar.sample"
9 | minSdkVersion 11
10 | targetSdkVersion 25
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | compile 'com.android.support:appcompat-v7:25.3.1'
25 | compile 'com.android.support:recyclerview-v7:25.3.1'
26 | compile project(':wavesidebar')
27 | }
28 |
--------------------------------------------------------------------------------
/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 /Users/gjz/Library/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 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gjiazhe/wavesidebar/sample/Contact.java:
--------------------------------------------------------------------------------
1 | package com.gjiazhe.wavesidebar.sample;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | /**
7 | * Created by gjz on 9/3/16.
8 | */
9 | public class Contact {
10 | private String index;
11 | private String name;
12 |
13 | public Contact(String index, String name) {
14 | this.index = index;
15 | this.name = name;
16 | }
17 |
18 | public String getIndex() {
19 | return index;
20 | }
21 |
22 | public String getName() {
23 | return name;
24 | }
25 |
26 | public static List getEnglishContacts() {
27 | List contacts = new ArrayList<>();
28 | contacts.add(new Contact("A", "Abbey"));
29 | contacts.add(new Contact("A", "Alex"));
30 | contacts.add(new Contact("A", "Amy"));
31 | contacts.add(new Contact("A", "Anne"));
32 | contacts.add(new Contact("B", "Betty"));
33 | contacts.add(new Contact("B", "Bob"));
34 | contacts.add(new Contact("B", "Brian"));
35 | contacts.add(new Contact("C", "Carl"));
36 | contacts.add(new Contact("C", "Candy"));
37 | contacts.add(new Contact("C", "Carlos"));
38 | contacts.add(new Contact("C", "Charles"));
39 | contacts.add(new Contact("C", "Christina"));
40 | contacts.add(new Contact("D", "David"));
41 | contacts.add(new Contact("D", "Daniel"));
42 | contacts.add(new Contact("E", "Elizabeth"));
43 | contacts.add(new Contact("E", "Eric"));
44 | contacts.add(new Contact("E", "Eva"));
45 | contacts.add(new Contact("F", "Frances"));
46 | contacts.add(new Contact("F", "Frank"));
47 | contacts.add(new Contact("I", "Ivy"));
48 | contacts.add(new Contact("J", "James"));
49 | contacts.add(new Contact("J", "John"));
50 | contacts.add(new Contact("J", "Jessica"));
51 | contacts.add(new Contact("K", "Karen"));
52 | contacts.add(new Contact("K", "Karl"));
53 | contacts.add(new Contact("K", "Kim"));
54 | contacts.add(new Contact("L", "Leon"));
55 | contacts.add(new Contact("L", "Lisa"));
56 | contacts.add(new Contact("P", "Paul"));
57 | contacts.add(new Contact("P", "Peter"));
58 | contacts.add(new Contact("S", "Sarah"));
59 | contacts.add(new Contact("S", "Steven"));
60 | contacts.add(new Contact("R", "Robert"));
61 | contacts.add(new Contact("R", "Ryan"));
62 | contacts.add(new Contact("T", "Tom"));
63 | contacts.add(new Contact("T", "Tony"));
64 | contacts.add(new Contact("W", "Wendy"));
65 | contacts.add(new Contact("W", "Will"));
66 | contacts.add(new Contact("W", "William"));
67 | contacts.add(new Contact("Z", "Zoe"));
68 | return contacts;
69 | }
70 |
71 | public static List getChineseContacts() {
72 | List contacts = new ArrayList<>();
73 | contacts.add(new Contact("B", "白虎"));
74 | contacts.add(new Contact("C", "常羲"));
75 | contacts.add(new Contact("C", "嫦娥"));
76 | contacts.add(new Contact("E", "二郎神"));
77 | contacts.add(new Contact("F", "伏羲"));
78 | contacts.add(new Contact("G", "观世音"));
79 | contacts.add(new Contact("J", "精卫"));
80 | contacts.add(new Contact("K", "夸父"));
81 | contacts.add(new Contact("N", "女娲"));
82 | contacts.add(new Contact("N", "哪吒"));
83 | contacts.add(new Contact("P", "盘古"));
84 | contacts.add(new Contact("Q", "青龙"));
85 | contacts.add(new Contact("R", "如来"));
86 | contacts.add(new Contact("S", "孙悟空"));
87 | contacts.add(new Contact("S", "沙僧"));
88 | contacts.add(new Contact("S", "顺风耳"));
89 | contacts.add(new Contact("T", "太白金星"));
90 | contacts.add(new Contact("T", "太上老君"));
91 | contacts.add(new Contact("X", "羲和"));
92 | contacts.add(new Contact("X", "玄武"));
93 | contacts.add(new Contact("Z", "猪八戒"));
94 | contacts.add(new Contact("Z", "朱雀"));
95 | contacts.add(new Contact("Z", "祝融"));
96 | return contacts;
97 | }
98 |
99 | public static List getJapaneseContacts() {
100 | List contacts = new ArrayList<>();
101 | contacts.add(new Contact("あ", "江户川コナン"));
102 | contacts.add(new Contact("あ", "油女シノ"));
103 | contacts.add(new Contact("あ", "犬夜叉"));
104 | contacts.add(new Contact("か", "旗木カカシ"));
105 | contacts.add(new Contact("か", "神楽"));
106 | contacts.add(new Contact("か", "黒崎一護"));
107 | contacts.add(new Contact("さ", "桜木花道"));
108 | contacts.add(new Contact("さ", "坂田銀時"));
109 | contacts.add(new Contact("さ", "殺生丸"));
110 | contacts.add(new Contact("な", "奈良シカマル"));
111 | contacts.add(new Contact("は", "旗木カカシ"));
112 | contacts.add(new Contact("は", "日向ネジ"));
113 | contacts.add(new Contact("や", "越前リョーマ"));
114 | contacts.add(new Contact("や", "野比のび太"));
115 | contacts.add(new Contact("や", "野原しんのすけ"));
116 | contacts.add(new Contact("ら", "流川楓"));
117 | return contacts;
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gjiazhe/wavesidebar/sample/ContactsAdapter.java:
--------------------------------------------------------------------------------
1 | package com.gjiazhe.wavesidebar.sample;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.ImageView;
8 | import android.widget.TextView;
9 |
10 | import java.util.List;
11 |
12 | /**
13 | * Created by gjz on 9/4/16.
14 | */
15 | public class ContactsAdapter extends RecyclerView.Adapter {
16 |
17 | private List contacts;
18 | private int layoutId;
19 |
20 | public ContactsAdapter(List contacts, int layoutId) {
21 | this.contacts = contacts;
22 | this.layoutId = layoutId;
23 | }
24 |
25 | @Override
26 | public ContactsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
27 | LayoutInflater inflater = LayoutInflater.from(parent.getContext());
28 | View view = inflater.inflate(layoutId, null);
29 | return new ContactsViewHolder(view);
30 | }
31 |
32 | @Override
33 | public void onBindViewHolder(ContactsViewHolder holder, int position) {
34 | Contact contact = contacts.get(position);
35 | if (position == 0 || !contacts.get(position-1).getIndex().equals(contact.getIndex())) {
36 | holder.tvIndex.setVisibility(View.VISIBLE);
37 | holder.tvIndex.setText(contact.getIndex());
38 | } else {
39 | holder.tvIndex.setVisibility(View.GONE);
40 | }
41 | holder.tvName.setText(contact.getName());
42 | }
43 |
44 | @Override
45 | public int getItemCount() {
46 | return contacts.size();
47 | }
48 |
49 | class ContactsViewHolder extends RecyclerView.ViewHolder {
50 | public TextView tvIndex;
51 | public ImageView ivAvatar;
52 | public TextView tvName;
53 |
54 | public ContactsViewHolder(View itemView) {
55 | super(itemView);
56 | tvIndex = (TextView) itemView.findViewById(R.id.tv_index);
57 | ivAvatar = (ImageView) itemView.findViewById(R.id.iv_avatar);
58 | tvName = (TextView) itemView.findViewById(R.id.tv_name);
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/gjiazhe/wavesidebar/sample/CustomIndexActivity.java:
--------------------------------------------------------------------------------
1 | package com.gjiazhe.wavesidebar.sample;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 | import android.support.v7.widget.LinearLayoutManager;
6 | import android.support.v7.widget.RecyclerView;
7 |
8 | import com.gjiazhe.wavesidebar.WaveSideBar;
9 |
10 | import java.util.ArrayList;
11 |
12 | public class CustomIndexActivity extends AppCompatActivity {
13 | private RecyclerView rvContacts;
14 | private WaveSideBar sideBar;
15 |
16 | private ArrayList contacts = new ArrayList<>();
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 |
22 | initData();
23 | initView();
24 | }
25 |
26 | private void initView() {
27 | setContentView(R.layout.activity_contacts);
28 | rvContacts = (RecyclerView) findViewById(R.id.rv_contacts);
29 | rvContacts.setLayoutManager(new LinearLayoutManager(this));
30 | rvContacts.setAdapter(new ContactsAdapter(contacts, R.layout.item_contacts));
31 |
32 | sideBar = (WaveSideBar) findViewById(R.id.side_bar);
33 | sideBar.setIndexItems("あ", "か", "さ", "た", "な", "は", "ま", "や", "ら", "わ");
34 | sideBar.setOnSelectIndexItemListener(new WaveSideBar.OnSelectIndexItemListener() {
35 | @Override
36 | public void onSelectIndexItem(String index) {
37 | for (int i=0; i contacts = new ArrayList<>();
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 |
22 | initData();
23 | initView();
24 | }
25 |
26 | private void initView() {
27 | setContentView(R.layout.activity_contacts);
28 | rvContacts = (RecyclerView) findViewById(R.id.rv_contacts);
29 | rvContacts.setLayoutManager(new LinearLayoutManager(this));
30 | rvContacts.setAdapter(new ContactsAdapter(contacts, R.layout.item_contacts_right));
31 |
32 | sideBar = (WaveSideBar) findViewById(R.id.side_bar);
33 | sideBar.setPosition(WaveSideBar.POSITION_LEFT);
34 | sideBar.setOnSelectIndexItemListener(new WaveSideBar.OnSelectIndexItemListener() {
35 | @Override
36 | public void onSelectIndexItem(String index) {
37 | for (int i=0; i contacts = new ArrayList<>();
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 |
23 | initData();
24 | initView();
25 | }
26 |
27 | private void initView() {
28 | setContentView(R.layout.activity_contacts);
29 | rvContacts = (RecyclerView) findViewById(R.id.rv_contacts);
30 | rvContacts.setLayoutManager(new LinearLayoutManager(this));
31 | rvContacts.setAdapter(new ContactsAdapter(contacts, R.layout.item_contacts));
32 | sideBar = (WaveSideBar) findViewById(R.id.side_bar);
33 | sideBar.setOnSelectIndexItemListener(new WaveSideBar.OnSelectIndexItemListener() {
34 | @Override
35 | public void onSelectIndexItem(String index) {
36 | for (int i=0; i
2 |
8 |
9 |
13 |
14 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
13 |
18 |
19 |
20 |
24 |
25 |
32 |
37 |
38 |
39 |
43 |
44 |
51 |
56 |
57 |
58 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_contacts.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
20 |
27 |
28 |
37 |
38 |
39 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_contacts_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
20 |
29 |
30 |
38 |
39 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gjiazhe/WaveSideBar/7e4f4c45ee033808d141d3d3c7e77fbe6f5455e0/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gjiazhe/WaveSideBar/7e4f4c45ee033808d141d3d3c7e77fbe6f5455e0/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gjiazhe/WaveSideBar/7e4f4c45ee033808d141d3d3c7e77fbe6f5455e0/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gjiazhe/WaveSideBar/7e4f4c45ee033808d141d3d3c7e77fbe6f5455e0/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gjiazhe/WaveSideBar/7e4f4c45ee033808d141d3d3c7e77fbe6f5455e0/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #795548
4 | #5D4037
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | WaveSideBar
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.3.0'
9 | classpath 'com.novoda:bintray-release:0.3.4'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | }
20 | }
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
25 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gjiazhe/WaveSideBar/7e4f4c45ee033808d141d3d3c7e77fbe6f5455e0/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Apr 04 23:11:36 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/screenshot/gif.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gjiazhe/WaveSideBar/7e4f4c45ee033808d141d3d3c7e77fbe6f5455e0/screenshot/gif.gif
--------------------------------------------------------------------------------
/screenshot/japanese1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gjiazhe/WaveSideBar/7e4f4c45ee033808d141d3d3c7e77fbe6f5455e0/screenshot/japanese1.png
--------------------------------------------------------------------------------
/screenshot/japanese2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gjiazhe/WaveSideBar/7e4f4c45ee033808d141d3d3c7e77fbe6f5455e0/screenshot/japanese2.png
--------------------------------------------------------------------------------
/screenshot/lazy_respond.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gjiazhe/WaveSideBar/7e4f4c45ee033808d141d3d3c7e77fbe6f5455e0/screenshot/lazy_respond.gif
--------------------------------------------------------------------------------
/screenshot/position_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gjiazhe/WaveSideBar/7e4f4c45ee033808d141d3d3c7e77fbe6f5455e0/screenshot/position_left.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':wavesidebar'
2 |
--------------------------------------------------------------------------------
/wavesidebar/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/wavesidebar/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.novoda.bintray-release'
3 |
4 | android {
5 | compileSdkVersion 25
6 | buildToolsVersion "25.0.2"
7 |
8 | defaultConfig {
9 | minSdkVersion 10
10 | targetSdkVersion 25
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:25.3.1'
25 | }
26 |
27 | publish {
28 | userOrg = 'gjiazhe'
29 | groupId = 'com.gjiazhe'
30 | artifactId = 'wavesidebar'
31 | publishVersion = '1.3'
32 | desc = ''
33 | website = 'https://github.com/gjiazhe/WaveSideBar'
34 | }
35 |
--------------------------------------------------------------------------------
/wavesidebar/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 /Users/gjz/Library/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 |
--------------------------------------------------------------------------------
/wavesidebar/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/wavesidebar/src/main/java/com/gjiazhe/wavesidebar/WaveSideBar.java:
--------------------------------------------------------------------------------
1 | package com.gjiazhe.wavesidebar;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.RectF;
9 | import android.util.AttributeSet;
10 | import android.util.DisplayMetrics;
11 | import android.util.TypedValue;
12 | import android.view.MotionEvent;
13 | import android.view.View;
14 |
15 | import java.util.Arrays;
16 |
17 | /**
18 | * Created by gjz on 8/23/16.
19 | */
20 | public class WaveSideBar extends View {
21 | private final static int DEFAULT_TEXT_SIZE = 14; // sp
22 | private final static int DEFAULT_MAX_OFFSET = 80; //dp
23 |
24 | private final static String[] DEFAULT_INDEX_ITEMS = {"A", "B", "C", "D", "E", "F", "G", "H", "I",
25 | "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
26 |
27 | private String[] mIndexItems;
28 |
29 | /**
30 | * the index in {@link #mIndexItems} of the current selected index item,
31 | * it's reset to -1 when the finger up
32 | */
33 | private int mCurrentIndex = -1;
34 |
35 | /**
36 | * Y coordinate of the point where finger is touching,
37 | * the baseline is top of {@link #mStartTouchingArea}
38 | * it's reset to -1 when the finger up
39 | */
40 | private float mCurrentY = -1;
41 |
42 | private Paint mPaint;
43 | private int mTextColor;
44 | private float mTextSize;
45 |
46 | /**
47 | * the height of each index item
48 | */
49 | private float mIndexItemHeight;
50 |
51 | /**
52 | * offset of the current selected index item
53 | */
54 | private float mMaxOffset;
55 |
56 | /**
57 | * {@link #mStartTouching} will be set to true when {@link MotionEvent#ACTION_DOWN}
58 | * happens in this area, and the side bar should start working.
59 | */
60 | private RectF mStartTouchingArea = new RectF();
61 |
62 | /**
63 | * height and width of {@link #mStartTouchingArea}
64 | */
65 | private float mBarHeight;
66 | private float mBarWidth;
67 |
68 | /**
69 | * Flag that the finger is starting touching.
70 | * If true, it means the {@link MotionEvent#ACTION_DOWN} happened but
71 | * {@link MotionEvent#ACTION_UP} not yet.
72 | */
73 | private boolean mStartTouching = false;
74 |
75 | /**
76 | * if true, the {@link OnSelectIndexItemListener#onSelectIndexItem(String)}
77 | * will not be called until the finger up.
78 | * if false, it will be called when the finger down, up and move.
79 | */
80 | private boolean mLazyRespond = false;
81 |
82 | /**
83 | * the position of the side bar, default is {@link #POSITION_RIGHT}.
84 | * You can set it to {@link #POSITION_LEFT} for people who use phone with left hand.
85 | */
86 | private int mSideBarPosition;
87 | public static final int POSITION_RIGHT = 0;
88 | public static final int POSITION_LEFT = 1;
89 |
90 | /**
91 | * the alignment of items, default is {@link #TEXT_ALIGN_CENTER}.
92 | */
93 | private int mTextAlignment;
94 | public static final int TEXT_ALIGN_CENTER = 0;
95 | public static final int TEXT_ALIGN_LEFT = 1;
96 | public static final int TEXT_ALIGN_RIGHT = 2;
97 |
98 |
99 | /**
100 | * observe the current selected index item
101 | */
102 | private OnSelectIndexItemListener onSelectIndexItemListener;
103 |
104 | /**
105 | * the baseline of the first index item text to draw
106 | */
107 | private float mFirstItemBaseLineY;
108 |
109 | /**
110 | * for {@link #dp2px(int)} and {@link #sp2px(int)}
111 | */
112 | private DisplayMetrics mDisplayMetrics;
113 |
114 |
115 | public WaveSideBar(Context context) {
116 | this(context, null);
117 | }
118 |
119 | public WaveSideBar(Context context, AttributeSet attrs) {
120 | this(context, attrs, 0);
121 | }
122 |
123 | public WaveSideBar(Context context, AttributeSet attrs, int defStyleAttr) {
124 | super(context, attrs, defStyleAttr);
125 | mDisplayMetrics = context.getResources().getDisplayMetrics();
126 |
127 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WaveSideBar);
128 | mLazyRespond = typedArray.getBoolean(R.styleable.WaveSideBar_sidebar_lazy_respond, false);
129 | mTextColor = typedArray.getColor(R.styleable.WaveSideBar_sidebar_text_color, Color.GRAY);
130 | mTextSize = typedArray.getDimension(R.styleable.WaveSideBar_sidebar_text_size, sp2px(DEFAULT_TEXT_SIZE));
131 | mMaxOffset = typedArray.getDimension(R.styleable.WaveSideBar_sidebar_max_offset, dp2px(DEFAULT_MAX_OFFSET));
132 | mSideBarPosition = typedArray.getInt(R.styleable.WaveSideBar_sidebar_position, POSITION_RIGHT);
133 | mTextAlignment = typedArray.getInt(R.styleable.WaveSideBar_sidebar_text_alignment, TEXT_ALIGN_CENTER);
134 | typedArray.recycle();
135 |
136 | mIndexItems = DEFAULT_INDEX_ITEMS;
137 |
138 | initPaint();
139 | }
140 |
141 | private void initPaint() {
142 | mPaint = new Paint();
143 | mPaint.setAntiAlias(true);
144 | mPaint.setColor(mTextColor);
145 | mPaint.setTextSize(mTextSize);
146 | switch (mTextAlignment) {
147 | case TEXT_ALIGN_CENTER: mPaint.setTextAlign(Paint.Align.CENTER); break;
148 | case TEXT_ALIGN_LEFT: mPaint.setTextAlign(Paint.Align.LEFT); break;
149 | case TEXT_ALIGN_RIGHT: mPaint.setTextAlign(Paint.Align.RIGHT); break;
150 | }
151 | }
152 |
153 | @Override
154 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
155 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
156 |
157 | int height = MeasureSpec.getSize(heightMeasureSpec);
158 | int width = MeasureSpec.getSize(widthMeasureSpec);
159 |
160 | Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
161 | mIndexItemHeight = fontMetrics.bottom - fontMetrics.top;
162 | mBarHeight = mIndexItems.length * mIndexItemHeight;
163 |
164 | // calculate the width of the longest text as the width of side bar
165 | for (String indexItem : mIndexItems) {
166 | mBarWidth = Math.max(mBarWidth, mPaint.measureText(indexItem));
167 | }
168 |
169 | float areaLeft = (mSideBarPosition == POSITION_LEFT) ? 0 : (width - mBarWidth - getPaddingRight());
170 | float areaRight = (mSideBarPosition == POSITION_LEFT) ? (getPaddingLeft() + areaLeft + mBarWidth) : width;
171 | float areaTop = height/2 - mBarHeight/2;
172 | float areaBottom = areaTop + mBarHeight;
173 | mStartTouchingArea.set(
174 | areaLeft,
175 | areaTop,
176 | areaRight,
177 | areaBottom);
178 |
179 | // the baseline Y of the first item' text to draw
180 | mFirstItemBaseLineY = (height/2 - mIndexItems.length*mIndexItemHeight/2)
181 | + (mIndexItemHeight/2 - (fontMetrics.descent-fontMetrics.ascent)/2)
182 | - fontMetrics.ascent;
183 | }
184 |
185 | @Override
186 | protected void onDraw(Canvas canvas) {
187 | super.onDraw(canvas);
188 |
189 | // draw each item
190 | for (int i = 0, mIndexItemsLength = mIndexItems.length; i < mIndexItemsLength; i++) {
191 | float baseLineY = mFirstItemBaseLineY + mIndexItemHeight*i;
192 |
193 | // calculate the scale factor of the item to draw
194 | float scale = getItemScale(i);
195 |
196 | int alphaScale = (i == mCurrentIndex) ? (255) : (int) (255 * (1-scale));
197 | mPaint.setAlpha(alphaScale);
198 |
199 | mPaint.setTextSize(mTextSize + mTextSize*scale);
200 |
201 | float baseLineX = 0f;
202 | if (mSideBarPosition == POSITION_LEFT) {
203 | switch (mTextAlignment) {
204 | case TEXT_ALIGN_CENTER:
205 | baseLineX = getPaddingLeft() + mBarWidth/2 + mMaxOffset*scale;
206 | break;
207 | case TEXT_ALIGN_LEFT:
208 | baseLineX = getPaddingLeft() + mMaxOffset*scale;
209 | break;
210 | case TEXT_ALIGN_RIGHT:
211 | baseLineX = getPaddingLeft() + mBarWidth + mMaxOffset*scale;
212 | break;
213 | }
214 | } else {
215 | switch (mTextAlignment) {
216 | case TEXT_ALIGN_CENTER:
217 | baseLineX = getWidth() - getPaddingRight() - mBarWidth/2 - mMaxOffset*scale;
218 | break;
219 | case TEXT_ALIGN_RIGHT:
220 | baseLineX = getWidth() - getPaddingRight() - mMaxOffset*scale;
221 | break;
222 | case TEXT_ALIGN_LEFT:
223 | baseLineX = getWidth() - getPaddingRight() - mBarWidth - mMaxOffset*scale;
224 | break;
225 | }
226 | }
227 |
228 | // draw
229 | canvas.drawText(
230 | mIndexItems[i], //item text to draw
231 | baseLineX, //baseLine X
232 | baseLineY, // baseLine Y
233 | mPaint);
234 | }
235 |
236 | // reset paint
237 | mPaint.setAlpha(255);
238 | mPaint.setTextSize(mTextSize);
239 | }
240 |
241 | /**
242 | * calculate the scale factor of the item to draw
243 | *
244 | * @param index the index of the item in array {@link #mIndexItems}
245 | * @return the scale factor of the item to draw
246 | */
247 | private float getItemScale(int index) {
248 | float scale = 0;
249 | if (mCurrentIndex != -1) {
250 | float distance = Math.abs(mCurrentY - (mIndexItemHeight*index+mIndexItemHeight/2)) / mIndexItemHeight;
251 | scale = 1 - distance*distance/16;
252 | scale = Math.max(scale, 0);
253 | }
254 | return scale;
255 | }
256 |
257 | @Override
258 | public boolean onTouchEvent(MotionEvent event) {
259 | if (mIndexItems.length == 0) {
260 | return super.onTouchEvent(event);
261 | }
262 |
263 | float eventY = event.getY();
264 | float eventX = event.getX();
265 | mCurrentIndex = getSelectedIndex(eventY);
266 |
267 | switch (event.getAction()) {
268 | case MotionEvent.ACTION_DOWN:
269 | if (mStartTouchingArea.contains(eventX, eventY)) {
270 | mStartTouching = true;
271 | if (!mLazyRespond && onSelectIndexItemListener != null) {
272 | onSelectIndexItemListener.onSelectIndexItem(mIndexItems[mCurrentIndex]);
273 | }
274 | invalidate();
275 | return true;
276 | } else {
277 | mCurrentIndex = -1;
278 | return false;
279 | }
280 |
281 | case MotionEvent.ACTION_MOVE:
282 | if (mStartTouching && !mLazyRespond && onSelectIndexItemListener != null) {
283 | onSelectIndexItemListener.onSelectIndexItem(mIndexItems[mCurrentIndex]);
284 | }
285 | invalidate();
286 | return true;
287 |
288 | case MotionEvent.ACTION_UP:
289 | case MotionEvent.ACTION_CANCEL:
290 | if (mLazyRespond && onSelectIndexItemListener != null) {
291 | onSelectIndexItemListener.onSelectIndexItem(mIndexItems[mCurrentIndex]);
292 | }
293 | mCurrentIndex = -1;
294 | mStartTouching = false;
295 | invalidate();
296 | return true;
297 | }
298 |
299 | return super.onTouchEvent(event);
300 | }
301 |
302 | private int getSelectedIndex(float eventY) {
303 | mCurrentY = eventY - (getHeight()/2 - mBarHeight /2);
304 | if (mCurrentY <= 0) {
305 | return 0;
306 | }
307 |
308 | int index = (int) (mCurrentY / this.mIndexItemHeight);
309 | if (index >= this.mIndexItems.length) {
310 | index = this.mIndexItems.length - 1;
311 | }
312 | return index;
313 | }
314 |
315 | private float dp2px(int dp) {
316 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, this.mDisplayMetrics);
317 | }
318 |
319 | private float sp2px(int sp) {
320 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, this.mDisplayMetrics);
321 | }
322 |
323 | public void setIndexItems(String... indexItems) {
324 | mIndexItems = Arrays.copyOf(indexItems, indexItems.length);
325 | requestLayout();
326 | }
327 |
328 | public void setTextColor(int color) {
329 | mTextColor = color;
330 | mPaint.setColor(color);
331 | invalidate();
332 | }
333 |
334 | public void setPosition(int position) {
335 | if (position != POSITION_RIGHT && position != POSITION_LEFT) {
336 | throw new IllegalArgumentException("the position must be POSITION_RIGHT or POSITION_LEFT");
337 | }
338 |
339 | mSideBarPosition = position;
340 | requestLayout();
341 | }
342 |
343 | public void setMaxOffset(int offset) {
344 | mMaxOffset = offset;
345 | invalidate();
346 | }
347 |
348 | public void setLazyRespond(boolean lazyRespond) {
349 | mLazyRespond = lazyRespond;
350 | }
351 |
352 | public void setTextAlign(int align) {
353 | if (mTextAlignment == align) {
354 | return;
355 | }
356 | switch (align) {
357 | case TEXT_ALIGN_CENTER: mPaint.setTextAlign(Paint.Align.CENTER); break;
358 | case TEXT_ALIGN_LEFT: mPaint.setTextAlign(Paint.Align.LEFT); break;
359 | case TEXT_ALIGN_RIGHT: mPaint.setTextAlign(Paint.Align.RIGHT); break;
360 | default:
361 | throw new IllegalArgumentException(
362 | "the alignment must be TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT or TEXT_ALIGN_RIGHT");
363 | }
364 | mTextAlignment = align;
365 | invalidate();
366 | }
367 |
368 | public void setTextSize(float size) {
369 | if (mTextSize == size) {
370 | return;
371 | }
372 | mTextSize = size;
373 | mPaint.setTextSize(size);
374 | invalidate();
375 | }
376 |
377 | public void setOnSelectIndexItemListener(OnSelectIndexItemListener onSelectIndexItemListener) {
378 | this.onSelectIndexItemListener = onSelectIndexItemListener;
379 | }
380 |
381 | public interface OnSelectIndexItemListener {
382 | void onSelectIndexItem(String index);
383 | }
384 | }
385 |
--------------------------------------------------------------------------------
/wavesidebar/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/wavesidebar/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | WaveSideBar
3 |
4 |
--------------------------------------------------------------------------------