Repository: evrencoskun/TableView Branch: master Commit: cf9c7f66e764 Files: 144 Total size: 527.6 KB Directory structure: gitextract_wgk_y394/ ├── .github/ │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ └── feature_request.md │ └── workflows/ │ └── validate_tableview.yml ├── .gitignore ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── evrencoskun/ │ │ └── tableviewsample/ │ │ ├── MainActivity.java │ │ ├── MainFragment.java │ │ └── tableview/ │ │ ├── TableViewAdapter.java │ │ ├── TableViewListener.java │ │ ├── TableViewModel.java │ │ ├── holder/ │ │ │ ├── CellViewHolder.java │ │ │ ├── ColumnHeaderViewHolder.java │ │ │ ├── GenderCellViewHolder.java │ │ │ ├── MoodCellViewHolder.java │ │ │ └── RowHeaderViewHolder.java │ │ ├── model/ │ │ │ ├── Cell.java │ │ │ ├── ColumnHeader.java │ │ │ └── RowHeader.java │ │ └── popup/ │ │ ├── ColumnHeaderLongPressPopup.java │ │ └── RowHeaderLongPressPopup.java │ └── res/ │ ├── drawable/ │ │ ├── ic_down.xml │ │ ├── ic_female.xml │ │ ├── ic_happy.xml │ │ ├── ic_male.xml │ │ ├── ic_next.xml │ │ ├── ic_previous.xml │ │ ├── ic_sad.xml │ │ └── ic_up.xml │ ├── layout/ │ │ ├── activity_main.xml │ │ ├── fragment_main.xml │ │ ├── table_view_cell_layout.xml │ │ ├── table_view_column_header_layout.xml │ │ ├── table_view_corner_layout.xml │ │ ├── table_view_image_cell_layout.xml │ │ └── table_view_row_header_layout.xml │ └── values/ │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── tableview/ ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src/ ├── androidTest/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── evrencoskun/ │ │ └── tableview/ │ │ └── test/ │ │ ├── CornerLayoutTest.java │ │ ├── CornerViewTest.java │ │ ├── ReverseLayoutTest.java │ │ ├── SimpleActivityTest.java │ │ ├── TestActivity.java │ │ ├── adapters/ │ │ │ ├── AbstractTableAdapterTest.java │ │ │ ├── CornerTestAdapter.java │ │ │ └── SimpleTestAdapter.java │ │ ├── data/ │ │ │ └── SimpleData.java │ │ ├── matchers/ │ │ │ └── ViewWidthMatcher.java │ │ └── models/ │ │ ├── Cell.java │ │ ├── ColumnHeader.java │ │ └── RowHeader.java │ └── res/ │ ├── layout/ │ │ ├── cell_layout.xml │ │ ├── column_layout.xml │ │ ├── corner_bottom_left.xml │ │ ├── corner_bottom_right.xml │ │ ├── corner_default.xml │ │ ├── corner_layout.xml │ │ ├── corner_top_left.xml │ │ ├── corner_top_right.xml │ │ ├── reverse_layout.xml │ │ └── row_layout.xml │ └── values/ │ ├── colors.xml │ └── dimens.xml └── main/ ├── AndroidManifest.xml ├── java/ │ └── com/ │ └── evrencoskun/ │ └── tableview/ │ ├── ITableView.java │ ├── TableView.java │ ├── adapter/ │ │ ├── AbstractTableAdapter.java │ │ ├── AdapterDataSetChangedListener.java │ │ ├── ITableAdapter.java │ │ └── recyclerview/ │ │ ├── AbstractRecyclerViewAdapter.java │ │ ├── CellRecyclerView.java │ │ ├── CellRecyclerViewAdapter.java │ │ ├── CellRowRecyclerViewAdapter.java │ │ ├── ColumnHeaderRecyclerViewAdapter.java │ │ ├── RowHeaderRecyclerViewAdapter.java │ │ └── holder/ │ │ ├── AbstractSorterViewHolder.java │ │ └── AbstractViewHolder.java │ ├── filter/ │ │ ├── Filter.java │ │ ├── FilterChangedListener.java │ │ ├── FilterItem.java │ │ ├── FilterType.java │ │ └── IFilterableModel.java │ ├── handler/ │ │ ├── ColumnSortHandler.java │ │ ├── ColumnWidthHandler.java │ │ ├── FilterHandler.java │ │ ├── PreferencesHandler.java │ │ ├── ScrollHandler.java │ │ ├── SelectionHandler.java │ │ └── VisibilityHandler.java │ ├── layoutmanager/ │ │ ├── CellLayoutManager.java │ │ ├── ColumnHeaderLayoutManager.java │ │ └── ColumnLayoutManager.java │ ├── listener/ │ │ ├── ITableViewListener.java │ │ ├── SimpleTableViewListener.java │ │ ├── TableViewLayoutChangeListener.java │ │ ├── itemclick/ │ │ │ ├── AbstractItemClickListener.java │ │ │ ├── CellRecyclerViewItemClickListener.java │ │ │ ├── ColumnHeaderRecyclerViewItemClickListener.java │ │ │ └── RowHeaderRecyclerViewItemClickListener.java │ │ └── scroll/ │ │ ├── HorizontalRecyclerViewListener.java │ │ └── VerticalRecyclerViewListener.java │ ├── pagination/ │ │ ├── IPagination.java │ │ └── Pagination.java │ ├── preference/ │ │ ├── Preferences.java │ │ └── SavedState.java │ ├── sort/ │ │ ├── AbstractSortComparator.java │ │ ├── ColumnForRowHeaderSortComparator.java │ │ ├── ColumnSortCallback.java │ │ ├── ColumnSortComparator.java │ │ ├── ColumnSortHelper.java │ │ ├── ColumnSortStateChangedListener.java │ │ ├── ISortableModel.java │ │ ├── RowHeaderForCellSortComparator.java │ │ ├── RowHeaderSortCallback.java │ │ ├── RowHeaderSortComparator.java │ │ ├── RowHeaderSortHelper.java │ │ └── SortState.java │ └── util/ │ └── TableViewUtils.java └── res/ ├── drawable/ │ └── cell_line_divider.xml └── values/ ├── attrs.xml ├── colors.xml ├── dimens.xml ├── ids.xml └── integers.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing to `TableView` ## Table of contents - [I need help/I have a question](#i-need-helpi-have-a-question) - [Reporting a bug](#reporting-a-bug) - [Suggest a new feature](#suggest-a-new-feature) - [Contributing to the project](#contributing-to-the-project) ## I need help/I have a question If you need help to integrate this library in your project, or if you have a generic question about it, please use [Stack Overflow](https://stackoverflow.com/questions/tagged/tableview+android), with the `TableView` and `Android` tags. ## Reporting a bug If the library is not behaving as you would expect, please [open a new issue](https://github.com/evrencoskun/TableView/issues/new?labels=bug&template=bug_report.md), after checking that it hasn't been reported yet. Don't forget to provide as much information as you can: - What are you trying to do? - What happened instead? - In case of crash, include the stacktace. - Which version of the library are you using? - Which version of Android is this issue happening on? ## Suggest a new feature If you would like a new feature to be added to the library, please [open a new issue](https://github.com/evrencoskun/TableView/issues/new?labels=enhancement&template=feature_request.md) to describe it. ## Contributing to the project Every contribution is welcome to the library. Simply [open a new Pull Request](https://github.com/evrencoskun/TableView/compare) with your changes, so they can be reviewed and merged into the project, and eventually released to everyone. Note that by making a contribution to this project you are agreeing to have your contributions governed by the [MIT License](https://github.com/evrencoskun/TableView/blob/master/LICENSE) copyright statement. This means that to the extent possible under law, you transfer all copyright and related or neighbouring rights of the code or documents you contribute to the project itself. You also represent that you have the authority to perform the above waiver with respect to the entirety of you contributions. ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: evrencoskun # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry custom: ['https://www.paypal.me/evrencoshkun'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Stacktrace** If applicable, add the stacktrace you encountered. **Tools:** - `TableView` version: [e.g. 0.8.9.2] - Android version: [e.g. 11] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ contact_links: - name: Stack Overflow url: https://stackoverflow.com/questions/tagged/tableview+android about: Please ask your questions here, with the `TableView` and `Android` tags ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/workflows/validate_tableview.yml ================================================ name: Validate TableView on: push: pull_request: jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Gradle Wrapper Validation uses: gradle/wrapper-validation-action@v1 - name: Setup JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 - name: Setup Cache uses: actions/cache@v2 with: path: | ~/.gradle/caches/ ~/.gradle/wrapper/ key: cache-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: cache-gradle- - name: Build run: ./gradlew :tableview:assembleDebug --no-daemon validate-sample: name: Validate sample app needs: build runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Setup JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 - name: Setup Cache uses: actions/cache@v2 with: path: | ~/.gradle/caches/ ~/.gradle/wrapper/ key: cache-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: cache-gradle- - name: Build run: ./gradlew :app:assembleDebug --no-daemon android-tests: name: Android Tests needs: build # Using macos-latest to take advantage of the hardware acceleration runs-on: macos-latest strategy: matrix: api-level: [ 15, 29 ] steps: - name: Checkout uses: actions/checkout@v2 - name: Setup JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 - name: Setup Cache uses: actions/cache@v2 with: path: | ~/.gradle/caches/ ~/.gradle/wrapper/ key: cache-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: cache-gradle- - name: Android Test - API ${{ matrix.api-level }} uses: ReactiveCircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} script: ./gradlew :tableview:connectedDebugAndroidTest --no-daemon ================================================ FILE: .gitignore ================================================ # Built application files *.apk *.ap_ # Mac .DS_Store # Files for the ART/Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ out/ # Gradle files .gradle/ build/ # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Log Files *.log # Android Studio Navigation editor temp files .navigation/ # Android Studio captures folder captures/ # Intellij *.iml *.idea/ # Keystore files *.jks # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild # Google Services (e.g. APIs or Firebase) google-services.json # Freeline freeline.py freeline/ freeline_project_description.json ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 Evren Coşkun Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

TableView for Android

TableView is a powerful Android library for displaying complex data structures and rendering tabular data composed of rows, columns and cells. TableView relies on a separate model object to hold and represent the data it displays. This repository also contains a sample app that is designed to show you how to create your own TableView in your application.

Full video »

## Features - [x] Each column width value can be calculated automatically considering the largest one. - [x] Setting your own model class to be displayed in a table view easily. - [x] `TableView` has an action listener interface to listen user touch interaction for each cell. - [x] `TableView` columns can be sorted in ascending or descending order. - [x] Hiding & showing the rows and columns is pretty easy. - [x] Filtering by more than one data. - [x] Pagination functionality. ## What's new You can check new implementations of `TableView` on the [release page](https://github.com/evrencoskun/TableView/releases). ## Table of Contents - [Installation](#installation) - [Documentation](#documentation) - [Sample Apps](#sample-apps) - [Donations](#donations) - [Contributors](#contributors) - [License](#license) ## Installation To use this library in your Android project, just add the following dependency into your module's `build.gradle`: ***Use Jitpack implementation*** 1. Check Jitpack use : ``` allprojects { repositories { ... maven { url 'https://jitpack.io' } } } ``` 2. Add implementation in project build : ``` implementation 'com.github.evrencoskun:TableView:v0.8.9.4' ``` ## Documentation Please check out the [project's wiki](https://github.com/evrencoskun/TableView/wiki). ## Sample Apps - This repository has a [sample application](https://github.com/evrencoskun/TableView/tree/master/app) of `TableView`. - [TableViewSample 2](https://github.com/evrencoskun/TableViewSample2) - [Xently-UI](https://github.com/ajharry69/Xently-UI) - [Price List Lite](https://pricelistlite.isolpro.in) - [Database Client for MySQL and PostgreSQL](https://play.google.com/store/apps/details?id=dev.dhruv.databaseclient) - ([Submit a Pull Request](https://github.com/evrencoskun/TableView/compare) to mention your app on this page) ## Donations **This project needs you!** If you would like to support this project's further development, the creator of this project or the continuous maintenance of this project, **feel free to donate**. Your donation is highly appreciated (and I love food, coffee and beer). Thank you! **PayPal** - [**Donate 5 $**](https://www.paypal.me/evrencoshkun): Thank's for creating this project, here's a coffee (or some beer) for you! - [**Donate 10 $**](https://www.paypal.me/evrencoshkun): Wow, I am stunned. Let me take you to the movies! - [**Donate 15 $**](https://www.paypal.me/evrencoshkun): I really appreciate your work, let's grab some lunch! - [**Donate 25 $**](https://www.paypal.me/evrencoshkun): That's some awesome stuff you did right there, dinner is on me! - Or you can also [**choose what you want to donate**](https://www.paypal.me/evrencoshkun), all donations are awesome! ## Contributors Contributions of any kind are welcome! I would like to thank all the [contributors](https://github.com/evrencoskun/TableView/graphs/contributors) for sharing code and making `TableView` a better product. If you wish to contribute to this project, please refer to our [contributing guide](.github/CONTRIBUTING.md). ## License ``` MIT License Copyright (c) 2021 Evren Coşkun Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ apply plugin: 'com.android.application' android { compileSdkVersion compile_sdk_version defaultConfig { applicationId 'com.evrencoskun.tableviewsample' minSdkVersion min_sdk_version targetSdkVersion target_sdk_version versionCode 1 versionName '1.0' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' vectorDrawables.useSupportLibrary = true } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility = java_version targetCompatibility = java_version } buildFeatures { buildConfig = false } lintOptions { abortOnError false } } dependencies { implementation project(path: ':tableview') implementation "androidx.annotation:annotation:$androidx_annotation_version" implementation "androidx.appcompat:appcompat:$androidx_appcompat_version" implementation "androidx.fragment:fragment:$androidx_fragment_version" implementation "androidx.recyclerview:recyclerview:$androidx_recyclerview_version" } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/evrencoskun/Library/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 *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/MainActivity.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { public MainActivity() { super(R.layout.activity_main); } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/MainFragment.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample; import android.os.Bundle; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.view.View; import android.widget.AdapterView; import android.widget.EditText; import android.widget.ImageButton; import android.widget.Spinner; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.evrencoskun.tableview.TableView; import com.evrencoskun.tableview.filter.Filter; import com.evrencoskun.tableview.pagination.Pagination; import com.evrencoskun.tableviewsample.tableview.TableViewAdapter; import com.evrencoskun.tableviewsample.tableview.TableViewListener; import com.evrencoskun.tableviewsample.tableview.TableViewModel; /** * A simple {@link Fragment} subclass. */ public class MainFragment extends Fragment { private Spinner moodFilter, genderFilter; private ImageButton previousButton, nextButton; private TextView tablePaginationDetails; private TableView mTableView; @Nullable private Filter mTableFilter; // This is used for filtering the table. @Nullable private Pagination mPagination; // This is used for paginating the table. private boolean mPaginationEnabled = false; public MainFragment() { super(R.layout.fragment_main); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { EditText searchField = view.findViewById(R.id.query_string); searchField.addTextChangedListener(mSearchTextWatcher); moodFilter = view.findViewById(R.id.mood_spinner); moodFilter.setOnItemSelectedListener(mItemSelectionListener); genderFilter = view.findViewById(R.id.gender_spinner); genderFilter.setOnItemSelectedListener(mItemSelectionListener); Spinner itemsPerPage = view.findViewById(R.id.items_per_page_spinner); View tableTestContainer = view.findViewById(R.id.table_test_container); previousButton = view.findViewById(R.id.previous_button); nextButton = view.findViewById(R.id.next_button); EditText pageNumberField = view.findViewById(R.id.page_number_text); tablePaginationDetails = view.findViewById(R.id.table_details); if (mPaginationEnabled) { tableTestContainer.setVisibility(View.VISIBLE); itemsPerPage.setOnItemSelectedListener(onItemsPerPageSelectedListener); previousButton.setOnClickListener(mClickListener); nextButton.setOnClickListener(mClickListener); pageNumberField.addTextChangedListener(onPageTextChanged); } else { tableTestContainer.setVisibility(View.GONE); } // Let's get TableView mTableView = view.findViewById(R.id.tableview); initializeTableView(); if (mPaginationEnabled) { mTableFilter = new Filter(mTableView); // Create an instance of a Filter and pass the // created TableView. // Create an instance for the TableView pagination and pass the created TableView. mPagination = new Pagination(mTableView); // Sets the pagination listener of the TableView pagination to handle // pagination actions. See onTableViewPageTurnedListener variable declaration below. mPagination.setOnTableViewPageTurnedListener(onTableViewPageTurnedListener); } } private void initializeTableView() { // Create TableView View model class to group view models of TableView TableViewModel tableViewModel = new TableViewModel(); // Create TableView Adapter TableViewAdapter tableViewAdapter = new TableViewAdapter(tableViewModel); mTableView.setAdapter(tableViewAdapter); mTableView.setTableViewListener(new TableViewListener(mTableView)); // Create an instance of a Filter and pass the TableView. //mTableFilter = new Filter(mTableView); // Load the dummy data to the TableView tableViewAdapter.setAllItems(tableViewModel.getColumnHeaderList(), tableViewModel .getRowHeaderList(), tableViewModel.getCellList()); //mTableView.setHasFixedWidth(true); /*for (int i = 0; i < mTableViewModel.getCellList().size(); i++) { mTableView.setColumnWidth(i, 200); }*) //mTableView.setColumnWidth(0, -2); //mTableView.setColumnWidth(1, -2); /*mTableView.setColumnWidth(2, 200); mTableView.setColumnWidth(3, 300); mTableView.setColumnWidth(4, 400); mTableView.setColumnWidth(5, 500);*/ } public void filterTable(@NonNull String filter) { // Sets a filter to the table, this will filter ALL the columns. if (mTableFilter != null) { mTableFilter.set(filter); } } public void filterTableForMood(@NonNull String filter) { // Sets a filter to the table, this will only filter a specific column. // In the example data, this will filter the mood column. if (mTableFilter != null) { mTableFilter.set(TableViewModel.MOOD_COLUMN_INDEX, filter); } } public void filterTableForGender(@NonNull String filter) { // Sets a filter to the table, this will only filter a specific column. // In the example data, this will filter the gender column. if (mTableFilter != null) { mTableFilter.set(TableViewModel.GENDER_COLUMN_INDEX, filter); } } // The following four methods below: nextTablePage(), previousTablePage(), // goToTablePage(int page) and setTableItemsPerPage(int itemsPerPage) // are for controlling the TableView pagination. public void nextTablePage() { if (mPagination != null) { mPagination.nextPage(); } } public void previousTablePage() { if (mPagination != null) { mPagination.previousPage(); } } public void goToTablePage(int page) { if (mPagination != null) { mPagination.goToPage(page); } } public void setTableItemsPerPage(int itemsPerPage) { if (mPagination != null) { mPagination.setItemsPerPage(itemsPerPage); } } // Handler for the changing of pages in the paginated TableView. @NonNull private final Pagination.OnTableViewPageTurnedListener onTableViewPageTurnedListener = new Pagination.OnTableViewPageTurnedListener() { @Override public void onPageTurned(int numItems, int itemsStart, int itemsEnd) { int currentPage = mPagination.getCurrentPage(); int pageCount = mPagination.getPageCount(); previousButton.setVisibility(View.VISIBLE); nextButton.setVisibility(View.VISIBLE); if (currentPage == 1 && pageCount == 1) { previousButton.setVisibility(View.INVISIBLE); nextButton.setVisibility(View.INVISIBLE); } if (currentPage == 1) { previousButton.setVisibility(View.INVISIBLE); } if (currentPage == pageCount) { nextButton.setVisibility(View.INVISIBLE); } tablePaginationDetails.setText(getString(R.string.table_pagination_details, String .valueOf(currentPage), String.valueOf(itemsStart), String.valueOf(itemsEnd))); } }; @NonNull private final AdapterView.OnItemSelectedListener mItemSelectionListener = new AdapterView .OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { // 0. index is for empty item of spinner. if (position > 0) { String filter = Integer.toString(position); if (parent == moodFilter) { filterTableForMood(filter); } else if (parent == genderFilter) { filterTableForGender(filter); } } } @Override public void onNothingSelected(AdapterView parent) { // Left empty intentionally. } }; @NonNull private final TextWatcher mSearchTextWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { filterTable(String.valueOf(s)); } @Override public void afterTextChanged(Editable s) { } }; @NonNull private final AdapterView.OnItemSelectedListener onItemsPerPageSelectedListener = new AdapterView .OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { int itemsPerPage; if ("All".equals(parent.getItemAtPosition(position).toString())) { itemsPerPage = 0; } else { itemsPerPage = Integer.parseInt(parent.getItemAtPosition(position).toString()); } setTableItemsPerPage(itemsPerPage); } @Override public void onNothingSelected(AdapterView parent) { } }; @NonNull private final View.OnClickListener mClickListener = new View.OnClickListener() { @Override public void onClick(View v) { if (v == previousButton) { previousTablePage(); } else if (v == nextButton) { nextTablePage(); } } }; @NonNull private final TextWatcher onPageTextChanged = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { int page; if (TextUtils.isEmpty(s)) { page = 1; } else { page = Integer.parseInt(String.valueOf(s)); } goToTablePage(page); } @Override public void afterTextChanged(Editable s) { } }; } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/TableViewAdapter.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.evrencoskun.tableview.adapter.AbstractTableAdapter; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.sort.SortState; import com.evrencoskun.tableviewsample.R; import com.evrencoskun.tableviewsample.tableview.holder.CellViewHolder; import com.evrencoskun.tableviewsample.tableview.holder.ColumnHeaderViewHolder; import com.evrencoskun.tableviewsample.tableview.holder.GenderCellViewHolder; import com.evrencoskun.tableviewsample.tableview.holder.MoodCellViewHolder; import com.evrencoskun.tableviewsample.tableview.holder.RowHeaderViewHolder; import com.evrencoskun.tableviewsample.tableview.model.Cell; import com.evrencoskun.tableviewsample.tableview.model.ColumnHeader; import com.evrencoskun.tableviewsample.tableview.model.RowHeader; /** * Created by evrencoskun on 11/06/2017. *

* This is a sample of custom TableView Adapter. */ public class TableViewAdapter extends AbstractTableAdapter { // Cell View Types by Column Position private static final int MOOD_CELL_TYPE = 1; private static final int GENDER_CELL_TYPE = 2; // add new one if it necessary.. private static final String LOG_TAG = TableViewAdapter.class.getSimpleName(); @NonNull private final TableViewModel mTableViewModel; public TableViewAdapter(@NonNull TableViewModel tableViewModel) { super(); this.mTableViewModel = tableViewModel; } /** * This is where you create your custom Cell ViewHolder. This method is called when Cell * RecyclerView of the TableView needs a new RecyclerView.ViewHolder of the given type to * represent an item. * * @param viewType : This value comes from "getCellItemViewType" method to support different * type of viewHolder as a Cell item. * @see #getCellItemViewType(int); */ @NonNull @Override public AbstractViewHolder onCreateCellViewHolder(@NonNull ViewGroup parent, int viewType) { //TODO check Log.e(LOG_TAG, " onCreateCellViewHolder has been called"); LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View layout; switch (viewType) { case MOOD_CELL_TYPE: // Get image cell layout which has ImageView on the base instead of TextView. layout = inflater.inflate(R.layout.table_view_image_cell_layout, parent, false); return new MoodCellViewHolder(layout); case GENDER_CELL_TYPE: // Get image cell layout which has ImageView instead of TextView. layout = inflater.inflate(R.layout.table_view_image_cell_layout, parent, false); return new GenderCellViewHolder(layout); default: // For cells that display a text layout = inflater.inflate(R.layout.table_view_cell_layout, parent, false); // Create a Cell ViewHolder return new CellViewHolder(layout); } } /** * That is where you set Cell View Model data to your custom Cell ViewHolder. This method is * Called by Cell RecyclerView of the TableView to display the data at the specified position. * This method gives you everything you need about a cell item. * * @param holder : This is one of your cell ViewHolders that was created on * ```onCreateCellViewHolder``` method. In this example we have created * "CellViewHolder" holder. * @param cellItemModel : This is the cell view model located on this X and Y position. In this * example, the model class is "Cell". * @param columnPosition : This is the X (Column) position of the cell item. * @param rowPosition : This is the Y (Row) position of the cell item. * @see #onCreateCellViewHolder(ViewGroup, int) ; */ @Override public void onBindCellViewHolder(@NonNull AbstractViewHolder holder, @Nullable Cell cellItemModel, int columnPosition, int rowPosition) { switch (holder.getItemViewType()) { case MOOD_CELL_TYPE: MoodCellViewHolder moodViewHolder = (MoodCellViewHolder) holder; moodViewHolder.cell_image.setImageResource(mTableViewModel.getDrawable((int) cellItemModel .getData(), false)); break; case GENDER_CELL_TYPE: GenderCellViewHolder genderViewHolder = (GenderCellViewHolder) holder; genderViewHolder.cell_image.setImageResource(mTableViewModel.getDrawable((int) cellItemModel.getData(), true)); break; default: // Get the holder to update cell item text CellViewHolder viewHolder = (CellViewHolder) holder; viewHolder.setCell(cellItemModel); break; } } /** * This is where you create your custom Column Header ViewHolder. This method is called when * Column Header RecyclerView of the TableView needs a new RecyclerView.ViewHolder of the given * type to represent an item. * * @param viewType : This value comes from "getColumnHeaderItemViewType" method to support * different type of viewHolder as a Column Header item. * @see #getColumnHeaderItemViewType(int); */ @NonNull @Override public AbstractViewHolder onCreateColumnHeaderViewHolder(@NonNull ViewGroup parent, int viewType) { // TODO: check //Log.e(LOG_TAG, " onCreateColumnHeaderViewHolder has been called"); // Get Column Header xml Layout View layout = LayoutInflater.from(parent.getContext()) .inflate(R.layout.table_view_column_header_layout, parent, false); // Create a ColumnHeader ViewHolder return new ColumnHeaderViewHolder(layout, getTableView()); } /** * That is where you set Column Header View Model data to your custom Column Header ViewHolder. * This method is Called by ColumnHeader RecyclerView of the TableView to display the data at * the specified position. This method gives you everything you need about a column header * item. * * @param holder : This is one of your column header ViewHolders that was created * on ```onCreateColumnHeaderViewHolder``` method. In this example * we have created "ColumnHeaderViewHolder" holder. * @param columnHeaderItemModel : This is the column header view model located on this X * position. In this example, the model class is "ColumnHeader". * @param columnPosition : This is the X (Column) position of the column header item. * @see #onCreateColumnHeaderViewHolder(ViewGroup, int) ; */ @Override public void onBindColumnHeaderViewHolder(@NonNull AbstractViewHolder holder, @Nullable ColumnHeader columnHeaderItemModel, int columnPosition) { // Get the holder to update cell item text ColumnHeaderViewHolder columnHeaderViewHolder = (ColumnHeaderViewHolder) holder; columnHeaderViewHolder.setColumnHeader(columnHeaderItemModel); } /** * This is where you create your custom Row Header ViewHolder. This method is called when * Row Header RecyclerView of the TableView needs a new RecyclerView.ViewHolder of the given * type to represent an item. * * @param viewType : This value comes from "getRowHeaderItemViewType" method to support * different type of viewHolder as a row Header item. * @see #getRowHeaderItemViewType(int); */ @NonNull @Override public AbstractViewHolder onCreateRowHeaderViewHolder(@NonNull ViewGroup parent, int viewType) { // Get Row Header xml Layout View layout = LayoutInflater.from(parent.getContext()) .inflate(R.layout.table_view_row_header_layout, parent, false); // Create a Row Header ViewHolder return new RowHeaderViewHolder(layout); } /** * That is where you set Row Header View Model data to your custom Row Header ViewHolder. This * method is Called by RowHeader RecyclerView of the TableView to display the data at the * specified position. This method gives you everything you need about a row header item. * * @param holder : This is one of your row header ViewHolders that was created on * ```onCreateRowHeaderViewHolder``` method. In this example we have * created "RowHeaderViewHolder" holder. * @param rowHeaderItemModel : This is the row header view model located on this Y position. In * this example, the model class is "RowHeader". * @param rowPosition : This is the Y (row) position of the row header item. * @see #onCreateRowHeaderViewHolder(ViewGroup, int) ; */ @Override public void onBindRowHeaderViewHolder(@NonNull AbstractViewHolder holder, @Nullable RowHeader rowHeaderItemModel, int rowPosition) { // Get the holder to update row header item text RowHeaderViewHolder rowHeaderViewHolder = (RowHeaderViewHolder) holder; rowHeaderViewHolder.row_header_textview.setText(String.valueOf(rowHeaderItemModel.getData())); } @NonNull @Override public View onCreateCornerView(@NonNull ViewGroup parent) { // Get Corner xml layout View corner = LayoutInflater.from(parent.getContext()) .inflate(R.layout.table_view_corner_layout, parent, false); corner.setOnClickListener(view -> { SortState sortState = TableViewAdapter.this.getTableView() .getRowHeaderSortingStatus(); if (sortState != SortState.ASCENDING) { Log.d("TableViewAdapter", "Order Ascending"); TableViewAdapter.this.getTableView().sortRowHeader(SortState.ASCENDING); } else { Log.d("TableViewAdapter", "Order Descending"); TableViewAdapter.this.getTableView().sortRowHeader(SortState.DESCENDING); } }); return corner; } @Override public int getColumnHeaderItemViewType(int position) { // The unique ID for this type of column header item // If you have different items for Cell View by X (Column) position, // then you should fill this method to be able create different // type of CellViewHolder on "onCreateCellViewHolder" return 0; } @Override public int getRowHeaderItemViewType(int position) { // The unique ID for this type of row header item // If you have different items for Row Header View by Y (Row) position, // then you should fill this method to be able create different // type of RowHeaderViewHolder on "onCreateRowHeaderViewHolder" return 0; } @Override public int getCellItemViewType(int column) { // The unique ID for this type of cell item // If you have different items for Cell View by X (Column) position, // then you should fill this method to be able create different // type of CellViewHolder on "onCreateCellViewHolder" switch (column) { case TableViewModel.MOOD_COLUMN_INDEX: return MOOD_CELL_TYPE; case TableViewModel.GENDER_COLUMN_INDEX: return GENDER_CELL_TYPE; default: // Default view type return 0; } } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/TableViewListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview; import android.content.Context; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.TableView; import com.evrencoskun.tableview.listener.ITableViewListener; import com.evrencoskun.tableviewsample.tableview.holder.ColumnHeaderViewHolder; import com.evrencoskun.tableviewsample.tableview.popup.ColumnHeaderLongPressPopup; import com.evrencoskun.tableviewsample.tableview.popup.RowHeaderLongPressPopup; /** * Created by evrencoskun on 21/09/2017. */ public class TableViewListener implements ITableViewListener { @NonNull private final Context mContext; @NonNull private final TableView mTableView; public TableViewListener(@NonNull TableView tableView) { this.mContext = tableView.getContext(); this.mTableView = tableView; } /** * Called when user click any cell item. * * @param cellView : Clicked Cell ViewHolder. * @param column : X (Column) position of Clicked Cell item. * @param row : Y (Row) position of Clicked Cell item. */ @Override public void onCellClicked(@NonNull RecyclerView.ViewHolder cellView, int column, int row) { // Do what you want. showToast("Cell " + column + " " + row + " has been clicked."); } /** * Called when user double click any cell item. * * @param cellView : Clicked Cell ViewHolder. * @param column : X (Column) position of Clicked Cell item. * @param row : Y (Row) position of Clicked Cell item. */ @Override public void onCellDoubleClicked(@NonNull RecyclerView.ViewHolder cellView, int column, int row) { // Do what you want. showToast("Cell " + column + " " + row + " has been double clicked."); } /** * Called when user long press any cell item. * * @param cellView : Long Pressed Cell ViewHolder. * @param column : X (Column) position of Long Pressed Cell item. * @param row : Y (Row) position of Long Pressed Cell item. */ @Override public void onCellLongPressed(@NonNull RecyclerView.ViewHolder cellView, final int column, int row) { // Do What you want showToast("Cell " + column + " " + row + " has been long pressed."); } /** * Called when user click any column header item. * * @param columnHeaderView : Clicked Column Header ViewHolder. * @param column : X (Column) position of Clicked Column Header item. */ @Override public void onColumnHeaderClicked(@NonNull RecyclerView.ViewHolder columnHeaderView, int column) { // Do what you want. showToast("Column header " + column + " has been clicked."); } /** * Called when user double click any column header item. * * @param columnHeaderView : Clicked Column Header ViewHolder. * @param column : X (Column) position of Clicked Column Header item. */ @Override public void onColumnHeaderDoubleClicked(@NonNull RecyclerView.ViewHolder columnHeaderView, int column) { // Do what you want. showToast("Column header " + column + " has been double clicked."); } /** * Called when user long press any column header item. * * @param columnHeaderView : Long Pressed Column Header ViewHolder. * @param column : X (Column) position of Long Pressed Column Header item. */ @Override public void onColumnHeaderLongPressed(@NonNull RecyclerView.ViewHolder columnHeaderView, int column) { if (columnHeaderView instanceof ColumnHeaderViewHolder) { // Create Long Press Popup ColumnHeaderLongPressPopup popup = new ColumnHeaderLongPressPopup( (ColumnHeaderViewHolder) columnHeaderView, mTableView); // Show popup.show(); } } /** * Called when user click any Row Header item. * * @param rowHeaderView : Clicked Row Header ViewHolder. * @param row : Y (Row) position of Clicked Row Header item. */ @Override public void onRowHeaderClicked(@NonNull RecyclerView.ViewHolder rowHeaderView, int row) { // Do whatever you want. showToast("Row header " + row + " has been clicked."); } /** * Called when user double click any Row Header item. * * @param rowHeaderView : Clicked Row Header ViewHolder. * @param row : Y (Row) position of Clicked Row Header item. */ @Override public void onRowHeaderDoubleClicked(@NonNull RecyclerView.ViewHolder rowHeaderView, int row) { // Do whatever you want. showToast("Row header " + row + " has been double clicked."); } /** * Called when user long press any row header item. * * @param rowHeaderView : Long Pressed Row Header ViewHolder. * @param row : Y (Row) position of Long Pressed Row Header item. */ @Override public void onRowHeaderLongPressed(@NonNull RecyclerView.ViewHolder rowHeaderView, int row) { // Create Long Press Popup RowHeaderLongPressPopup popup = new RowHeaderLongPressPopup(rowHeaderView, mTableView); // Show popup.show(); } private void showToast(String p_strMessage) { Toast.makeText(mContext, p_strMessage, Toast.LENGTH_SHORT).show(); } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/TableViewModel.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import com.evrencoskun.tableviewsample.R; import com.evrencoskun.tableviewsample.tableview.model.Cell; import com.evrencoskun.tableviewsample.tableview.model.ColumnHeader; import com.evrencoskun.tableviewsample.tableview.model.RowHeader; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * Created by evrencoskun on 4.02.2018. */ public class TableViewModel { // Columns indexes public static final int MOOD_COLUMN_INDEX = 3; public static final int GENDER_COLUMN_INDEX = 4; // Constant values for icons public static final int SAD = 1; public static final int HAPPY = 2; public static final int BOY = 1; public static final int GIRL = 2; // Constant size for dummy data sets private static final int COLUMN_SIZE = 500; private static final int ROW_SIZE = 500; // Drawables @DrawableRes private final int mBoyDrawable; @DrawableRes private final int mGirlDrawable; @DrawableRes private final int mHappyDrawable; @DrawableRes private final int mSadDrawable; public TableViewModel() { // initialize drawables mBoyDrawable = R.drawable.ic_male; mGirlDrawable = R.drawable.ic_female; mHappyDrawable = R.drawable.ic_happy; mSadDrawable = R.drawable.ic_sad; } @NonNull private List getSimpleRowHeaderList() { List list = new ArrayList<>(); for (int i = 0; i < ROW_SIZE; i++) { RowHeader header = new RowHeader(String.valueOf(i), "row " + i); list.add(header); } return list; } /** * This is a dummy model list test some cases. */ @NonNull private List getRandomColumnHeaderList() { List list = new ArrayList<>(); for (int i = 0; i < COLUMN_SIZE; i++) { String title = "column " + i; int nRandom = new Random().nextInt(); if (nRandom % 4 == 0 || nRandom % 3 == 0 || nRandom == i) { title = "large column " + i; } ColumnHeader header = new ColumnHeader(String.valueOf(i), title); list.add(header); } return list; } /** * This is a dummy model list test some cases. */ @NonNull private List> getCellListForSortingTest() { List> list = new ArrayList<>(); for (int i = 0; i < ROW_SIZE; i++) { List cellList = new ArrayList<>(); for (int j = 0; j < COLUMN_SIZE; j++) { Object text = "cell " + j + " " + i; final int random = new Random().nextInt(); if (j == 0) { text = i; } else if (j == 1) { text = random; } else if (j == MOOD_COLUMN_INDEX) { text = random % 2 == 0 ? HAPPY : SAD; } else if (j == GENDER_COLUMN_INDEX) { text = random % 2 == 0 ? BOY : GIRL; } // Create dummy id. String id = j + "-" + i; Cell cell; if (j == 3) { cell = new Cell(id, text); } else if (j == 4) { // NOTE female and male keywords for filter will have conflict since "female" // contains "male" cell = new Cell(id, text); } else { cell = new Cell(id, text); } cellList.add(cell); } list.add(cellList); } return list; } @DrawableRes public int getDrawable(int value, boolean isGender) { if (isGender) { return value == BOY ? mBoyDrawable : mGirlDrawable; } else { return value == SAD ? mSadDrawable : mHappyDrawable; } } @NonNull public List> getCellList() { return getCellListForSortingTest(); } @NonNull public List getRowHeaderList() { return getSimpleRowHeaderList(); } @NonNull public List getColumnHeaderList() { return getRandomColumnHeaderList(); } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/holder/CellViewHolder.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview.holder; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableviewsample.R; import com.evrencoskun.tableviewsample.tableview.model.Cell; /** * Created by evrencoskun on 23/10/2017. */ public class CellViewHolder extends AbstractViewHolder { @NonNull private final TextView cell_textview; @NonNull private final LinearLayout cell_container; public CellViewHolder(@NonNull View itemView) { super(itemView); cell_textview = itemView.findViewById(R.id.cell_data); cell_container = itemView.findViewById(R.id.cell_container); } public void setCell(@Nullable Cell cell) { cell_textview.setText(String.valueOf(cell.getData())); // If your TableView should have auto resize for cells & columns. // Then you should consider the below lines. Otherwise, you can ignore them. // It is necessary to remeasure itself. cell_container.getLayoutParams().width = LinearLayout.LayoutParams.WRAP_CONTENT; cell_textview.requestLayout(); } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/holder/ColumnHeaderViewHolder.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview.holder; import android.util.Log; import android.view.View; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractSorterViewHolder; import com.evrencoskun.tableview.sort.SortState; import com.evrencoskun.tableviewsample.R; import com.evrencoskun.tableviewsample.tableview.model.ColumnHeader; /** * Created by evrencoskun on 23/10/2017. */ public class ColumnHeaderViewHolder extends AbstractSorterViewHolder { private static final String LOG_TAG = ColumnHeaderViewHolder.class.getSimpleName(); @NonNull private final LinearLayout column_header_container; @NonNull private final TextView column_header_textview; @NonNull private final ImageButton column_header_sortButton; @Nullable private final ITableView tableView; public ColumnHeaderViewHolder(@NonNull View itemView, @Nullable ITableView tableView) { super(itemView); this.tableView = tableView; column_header_textview = itemView.findViewById(R.id.column_header_textView); column_header_container = itemView.findViewById(R.id.column_header_container); column_header_sortButton = itemView.findViewById(R.id.column_header_sortButton); // Set click listener to the sort button column_header_sortButton.setOnClickListener(mSortButtonClickListener); } /** * This method is calling from onBindColumnHeaderHolder on TableViewAdapter */ public void setColumnHeader(@Nullable ColumnHeader columnHeader) { column_header_textview.setText(String.valueOf(columnHeader.getData())); // If your TableView should have auto resize for cells & columns. // Then you should consider the below lines. Otherwise, you can remove them. // It is necessary to remeasure itself. column_header_container.getLayoutParams().width = LinearLayout.LayoutParams.WRAP_CONTENT; column_header_textview.requestLayout(); } @NonNull private final View.OnClickListener mSortButtonClickListener = new View.OnClickListener() { @Override public void onClick(View view) { if (getSortState() == SortState.ASCENDING) { tableView.sortColumn(getBindingAdapterPosition(), SortState.DESCENDING); } else if (getSortState() == SortState.DESCENDING) { tableView.sortColumn(getBindingAdapterPosition(), SortState.ASCENDING); } else { // Default one tableView.sortColumn(getBindingAdapterPosition(), SortState.DESCENDING); } } }; @Override public void onSortingStatusChanged(@NonNull SortState sortState) { Log.e(LOG_TAG, " + onSortingStatusChanged: x: " + getBindingAdapterPosition() + ", " + "old state: " + getSortState() + ", current state: " + sortState + ", " + "visibility: " + column_header_sortButton.getVisibility()); super.onSortingStatusChanged(sortState); // It is necessary to remeasure itself. column_header_container.getLayoutParams().width = LinearLayout.LayoutParams.WRAP_CONTENT; controlSortState(sortState); Log.e(LOG_TAG, " - onSortingStatusChanged: x: " + getBindingAdapterPosition() + ", " + "old state: " + getSortState() + ", current state: " + sortState + ", " + "visibility: " + column_header_sortButton.getVisibility()); column_header_textview.requestLayout(); column_header_sortButton.requestLayout(); column_header_container.requestLayout(); itemView.requestLayout(); } private void controlSortState(@NonNull SortState sortState) { if (sortState == SortState.ASCENDING) { column_header_sortButton.setVisibility(View.VISIBLE); column_header_sortButton.setImageResource(R.drawable.ic_down); } else if (sortState == SortState.DESCENDING) { column_header_sortButton.setVisibility(View.VISIBLE); column_header_sortButton.setImageResource(R.drawable.ic_up); } else { column_header_sortButton.setVisibility(View.INVISIBLE); } } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/holder/GenderCellViewHolder.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview.holder; import android.view.View; import androidx.annotation.NonNull; import com.evrencoskun.tableviewsample.R; import com.evrencoskun.tableviewsample.tableview.TableViewModel; /** * Created by evrencoskun on 4.02.2018. */ public class GenderCellViewHolder extends MoodCellViewHolder { public GenderCellViewHolder(@NonNull View itemView) { super(itemView); } @Override public void setData(Object data) { int gender = (int) data; int genderDrawable = gender == TableViewModel.BOY ? R.drawable.ic_male : R.drawable.ic_female; cell_image.setImageResource(genderDrawable); } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/holder/MoodCellViewHolder.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview.holder; import android.view.View; import android.widget.ImageView; import androidx.annotation.NonNull; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableviewsample.R; import com.evrencoskun.tableviewsample.tableview.TableViewModel; /** * Created by evrencoskun on 4.02.2018. */ public class MoodCellViewHolder extends AbstractViewHolder { @NonNull public final ImageView cell_image; public MoodCellViewHolder(@NonNull View itemView) { super(itemView); cell_image = itemView.findViewById(R.id.cell_image); } public void setData(Object data) { int mood = (int) data; int moodDrawable = mood == TableViewModel.HAPPY ? R.drawable.ic_happy : R.drawable.ic_sad; cell_image.setImageResource(moodDrawable); } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/holder/RowHeaderViewHolder.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview.holder; import android.view.View; import android.widget.TextView; import androidx.annotation.NonNull; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableviewsample.R; /** * Created by evrencoskun on 23/10/2017. */ public class RowHeaderViewHolder extends AbstractViewHolder { @NonNull public final TextView row_header_textview; public RowHeaderViewHolder(@NonNull View itemView) { super(itemView); row_header_textview = itemView.findViewById(R.id.row_header_textview); } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/model/Cell.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview.model; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.evrencoskun.tableview.filter.IFilterableModel; import com.evrencoskun.tableview.sort.ISortableModel; /** * Created by evrencoskun on 11/06/2017. */ public class Cell implements ISortableModel, IFilterableModel { @NonNull private final String mId; @Nullable private final Object mData; @NonNull private final String mFilterKeyword; public Cell(@NonNull String id, @Nullable Object data) { this.mId = id; this.mData = data; this.mFilterKeyword = String.valueOf(data); } /** * This is necessary for sorting process. * See ISortableModel */ @NonNull @Override public String getId() { return mId; } /** * This is necessary for sorting process. * See ISortableModel */ @Nullable @Override public Object getContent() { return mData; } @Nullable public Object getData() { return mData; } @NonNull @Override public String getFilterableKeyword() { return mFilterKeyword; } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/model/ColumnHeader.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview.model; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * Created by evrencoskun on 11/06/2017. */ public class ColumnHeader extends Cell { public ColumnHeader(@NonNull String id, @Nullable String data) { super(id, data); } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/model/RowHeader.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview.model; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * Created by evrencoskun on 11/06/2017. */ public class RowHeader extends Cell { public RowHeader(@NonNull String id, @Nullable String data) { super(id, data); } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/popup/ColumnHeaderLongPressPopup.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview.popup; import android.content.Context; import android.view.Menu; import android.view.MenuItem; import android.widget.PopupMenu; import androidx.annotation.NonNull; import com.evrencoskun.tableview.TableView; import com.evrencoskun.tableview.sort.SortState; import com.evrencoskun.tableviewsample.R; import com.evrencoskun.tableviewsample.tableview.holder.ColumnHeaderViewHolder; /** * Created by evrencoskun on 24.12.2017. */ public class ColumnHeaderLongPressPopup extends PopupMenu implements PopupMenu .OnMenuItemClickListener { // Menu Item constants private static final int ASCENDING = 1; private static final int DESCENDING = 2; private static final int HIDE_ROW = 3; private static final int SHOW_ROW = 4; private static final int SCROLL_ROW = 5; @NonNull private final TableView mTableView; private final int mXPosition; public ColumnHeaderLongPressPopup(@NonNull ColumnHeaderViewHolder viewHolder, @NonNull TableView tableView) { super(viewHolder.itemView.getContext(), viewHolder.itemView); this.mTableView = tableView; this.mXPosition = viewHolder.getBindingAdapterPosition(); initialize(); } private void initialize() { createMenuItem(); changeMenuItemVisibility(); this.setOnMenuItemClickListener(this); } private void createMenuItem() { Context context = mTableView.getContext(); this.getMenu().add(Menu.NONE, ASCENDING, 0, context.getString(R.string.sort_ascending)); this.getMenu().add(Menu.NONE, DESCENDING, 1, context.getString(R.string.sort_descending)); this.getMenu().add(Menu.NONE, HIDE_ROW, 2, context.getString(R.string.hiding_row_sample)); this.getMenu().add(Menu.NONE, SHOW_ROW, 3, context.getString(R.string.showing_row_sample)); this.getMenu().add(Menu.NONE, SCROLL_ROW, 4, context.getString(R.string.scroll_to_row_position)); this.getMenu().add(Menu.NONE, SCROLL_ROW, 0, "change width"); // add new one ... } private void changeMenuItemVisibility() { // Determine which one shouldn't be visible SortState sortState = mTableView.getSortingStatus(mXPosition); if (sortState == SortState.UNSORTED) { // Show others } else if (sortState == SortState.DESCENDING) { // Hide DESCENDING menu item getMenu().getItem(1).setVisible(false); } else if (sortState == SortState.ASCENDING) { // Hide ASCENDING menu item getMenu().getItem(0).setVisible(false); } } @Override public boolean onMenuItemClick(MenuItem menuItem) { // Note: item id is index of menu item.. switch (menuItem.getItemId()) { case ASCENDING: mTableView.sortColumn(mXPosition, SortState.ASCENDING); break; case DESCENDING: mTableView.sortColumn(mXPosition, SortState.DESCENDING); break; case HIDE_ROW: mTableView.hideRow(5); break; case SHOW_ROW: mTableView.showRow(5); break; case SCROLL_ROW: mTableView.scrollToRowPosition(5); break; } return true; } } ================================================ FILE: app/src/main/java/com/evrencoskun/tableviewsample/tableview/popup/RowHeaderLongPressPopup.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableviewsample.tableview.popup; import android.content.Context; import android.view.Menu; import android.view.MenuItem; import android.widget.PopupMenu; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.TableView; import com.evrencoskun.tableviewsample.R; /** * Created by evrencoskun on 21.01.2018. */ public class RowHeaderLongPressPopup extends PopupMenu implements PopupMenu .OnMenuItemClickListener { // Menu Item constants private static final int SCROLL_COLUMN = 1; private static final int SHOWHIDE_COLUMN = 2; private static final int REMOVE_ROW = 3; @NonNull private final TableView mTableView; private final int mRowPosition; public RowHeaderLongPressPopup(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull TableView tableView) { super(viewHolder.itemView.getContext(), viewHolder.itemView); this.mTableView = tableView; this.mRowPosition = viewHolder.getBindingAdapterPosition(); initialize(); } private void initialize() { createMenuItem(); this.setOnMenuItemClickListener(this); } private void createMenuItem() { Context context = mTableView.getContext(); this.getMenu().add(Menu.NONE, SCROLL_COLUMN, 0, context.getString(R.string .scroll_to_column_position)); this.getMenu().add(Menu.NONE, SHOWHIDE_COLUMN, 1, context.getString(R.string .show_hide_the_column)); this.getMenu().add(Menu.NONE, REMOVE_ROW, 2, "Remove " + mRowPosition + " position"); // add new one ... } @Override public boolean onMenuItemClick(MenuItem menuItem) { // Note: item id is index of menu item.. switch (menuItem.getItemId()) { case SCROLL_COLUMN: mTableView.scrollToColumnPosition(15); break; case SHOWHIDE_COLUMN: int column = 1; if (mTableView.isColumnVisible(column)) { mTableView.hideColumn(column); } else { mTableView.showColumn(column); } break; case REMOVE_ROW: mTableView.getAdapter().removeRow(mRowPosition); break; } return true; } } ================================================ FILE: app/src/main/res/drawable/ic_down.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_female.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_happy.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_male.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_next.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_previous.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_sad.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_up.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: app/src/main/res/layout/fragment_main.xml ================================================ ================================================ FILE: app/src/main/res/layout/table_view_cell_layout.xml ================================================ ================================================ FILE: app/src/main/res/layout/table_view_column_header_layout.xml ================================================ ================================================ FILE: app/src/main/res/layout/table_view_corner_layout.xml ================================================ ================================================ FILE: app/src/main/res/layout/table_view_image_cell_layout.xml ================================================ ================================================ FILE: app/src/main/res/layout/table_view_row_header_layout.xml ================================================ ================================================ FILE: app/src/main/res/values/colors.xml ================================================ #3F51B5 #303F9F #FF4081 #E7E7E7 #ffffff @android:color/holo_red_light #0a0a0a ================================================ FILE: app/src/main/res/values/dimens.xml ================================================ 40dp 55dp 12sp 55dp 40dp ================================================ FILE: app/src/main/res/values/strings.xml ================================================ TableViewSample Sort Ascending Sort Descending Hiding 3. Row Sample Showing 3. Row Sample Scroll to 15. column position Scroll to 5. row position Showing / Hiding 1. column Page %s, showing items %s - %s. Happy Sad Male Female 10 20 25 50 All ================================================ FILE: app/src/main/res/values/styles.xml ================================================ ================================================ FILE: build.gradle ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.android_gradle_plugin_version = '4.2.1' ext.androidx_annotation_version = '1.2.0' ext.androidx_appcompat_version = '1.3.0' ext.androidx_core_version = '1.5.0' ext.androidx_fragment_version = '1.3.5' ext.androidx_recyclerview_version = '1.2.1' ext.androidx_test_version = '1.3.0' ext.androidx_test_espresso_version = '3.3.0' ext.androidx_test_ext_version = '1.1.2' ext.compile_sdk_version = 29 ext.java_version = '1.8' ext.junit_version = '4.13.2' ext.min_sdk_version = 14 ext.target_sdk_version = 29 repositories { jcenter() google() } dependencies { classpath "com.android.tools.build:gradle:$android_gradle_plugin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() google() } } task clean(type: Delete) { delete rootProject.buildDir } subprojects { if (project.getGradle().startParameter.taskNames.any{it.contains('bintrayUpload')} && project.name in ['tableview']) { println project.name apply plugin: 'maven' gradle.taskGraph.whenReady { taskGraph -> def pomTask = taskGraph.getAllTasks().find { it.path == ":$project.name:generatePomFileForReleasePublication" } if (pomTask == null) println 'pomTask null' if (pomTask == null) return pomTask.doLast { println 'Updating pom-file(s) with License info' pomTask.outputs.files .filter { File file -> file.path.contains("publications") && file.name.matches("^pom-.+\\.xml\$") } .forEach { File file -> addLicense(file) } } } } } static void addLicense(File pom) { def licenseNode = new Node(null, "license") licenseNode.append(new Node(null, "name", "MIT")) licenseNode.append(new Node(null, "url", "https://github.com/evrencoskun/TableView/blob/master/LICENSE")) def licensesNode = new Node(null, "licenses") licensesNode.append(licenseNode) def xml = new XmlParser().parse(pom) xml.append(licensesNode) def writer = new PrintWriter(new FileWriter(pom)) def printer = new XmlNodePrinter(writer) printer.preserveWhitespace = true printer.print(xml) writer.close() } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ # # MIT License # # Copyright (c) 2021 Evren Coşkun # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ 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. org.gradle.jvmargs=-Xmx1536m # 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 android.useAndroidX=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # 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 APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # 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 nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac 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" -a "$nonstop" = "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 or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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 # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @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 set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @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="-Xmx64m" "-Xms64m" @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 execute 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 execute 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 :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 %* :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: settings.gradle ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ include ':app', ':tableview' ================================================ FILE: tableview/.gitignore ================================================ /build ================================================ FILE: tableview/build.gradle ================================================ apply plugin: 'com.android.library' android { compileSdkVersion compile_sdk_version defaultConfig { minSdkVersion min_sdk_version targetSdkVersion target_sdk_version versionCode 1 versionName '0.8.9.4' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' testCoverageEnabled true } } compileOptions { sourceCompatibility = java_version targetCompatibility = java_version } buildFeatures { buildConfig = false } lintOptions { abortOnError false } tasks.withType(Javadoc) { options.addStringOption('Xdoclint:none', '-quiet') options.addStringOption('encoding', 'UTF-8') options.addStringOption('charSet', 'UTF-8') } } dependencies { implementation "androidx.annotation:annotation:$androidx_annotation_version" implementation "androidx.recyclerview:recyclerview:$androidx_recyclerview_version" testImplementation "junit:junit:$junit_version" androidTestImplementation "androidx.appcompat:appcompat:$androidx_appcompat_version" androidTestImplementation "androidx.test:rules:$androidx_test_version" androidTestImplementation "androidx.test:runner:$androidx_test_version" androidTestImplementation "androidx.test.espresso:espresso-core:$androidx_test_espresso_version" androidTestImplementation "androidx.test.espresso:espresso-contrib:$androidx_test_espresso_version" androidTestImplementation "androidx.test.ext:junit:$androidx_test_ext_version" androidTestImplementation "junit:junit:$junit_version" } // Configure the publishing apply plugin: 'maven-publish' task androidJavadocs(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) android.libraryVariants.all { variant -> if (variant.name == 'release') { owner.classpath += variant.javaCompileProvider.get().classpath } } exclude '**/R.html', '**/R.*.html', '**/index.html' } task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { archiveClassifier.set('javadoc') from androidJavadocs.destinationDir } task androidSourcesJar(type: Jar) { archiveClassifier.set('sources') from android.sourceSets.main.java.srcDirs } afterEvaluate { publishing { publications { release(MavenPublication) { from components.release groupId = 'com.evrencoskun.library' artifactId = 'tableview' version = android.defaultConfig.versionName artifact androidJavadocsJar artifact androidSourcesJar pom { name = 'TableView' description = 'TableView is a powerful Android library for displaying complex data structures and rendering tabular data composed of rows, columns and cells.' url = 'https://github.com/evrencoskun/TableView' } } } repositories { maven { url = 'https://api.bintray.com/content/evrencoskun/artifact-sandbox/' } } } } ================================================ FILE: tableview/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/evrencoskun/Library/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 *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: tableview/src/androidTest/AndroidManifest.xml ================================================ ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/CornerLayoutTest.java ================================================ /* * MIT License * * Copyright (c) 2021 Andrew Beck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.test; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.PositionAssertions.isCompletelyAbove; import static androidx.test.espresso.assertion.PositionAssertions.isCompletelyBelow; import static androidx.test.espresso.assertion.PositionAssertions.isCompletelyLeftOf; import static androidx.test.espresso.assertion.PositionAssertions.isCompletelyRightOf; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withParent; import static androidx.test.espresso.matcher.ViewMatchers.withParentIndex; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; import android.widget.RelativeLayout; import androidx.test.espresso.matcher.ViewMatchers; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.TableView; import com.evrencoskun.tableview.test.adapters.CornerTestAdapter; import com.evrencoskun.tableview.test.data.SimpleData; import com.evrencoskun.tableview.test.matchers.ViewWidthMatcher; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class CornerLayoutTest { @Rule public ActivityScenarioRule mActivityTestRule = new ActivityScenarioRule<>(TestActivity.class); @Test public void testDefaultCorner() { mActivityTestRule.getScenario() .onActivity(activity -> { activity.setContentView(R.layout.corner_default); TableView tableView = activity.findViewById(R.id.tableview); CornerTestAdapter cornerTestAdapter = new CornerTestAdapter(); tableView.setAdapter(cornerTestAdapter); SimpleData simpleData = new SimpleData(5); cornerTestAdapter.setAllItems( simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); }); // Check that the corner view is created and visible // The Corner view uses cell_layout which has RelativeLayout as top item onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text // The first child of the RelativeLayout is a textView (index starts at zero) onView(allOf(withParent(withId(R.id.corner_view)), withParentIndex(0))) .check(matches(withText("Corner"))); // Check that Corner to is to the right of the column headers onView(withId(R.id.corner_view)).check(isCompletelyLeftOf(withId(R.id.ColumnHeaderRecyclerView))); // Check that Corner to is to the above of the row headers onView(withId(R.id.corner_view)).check(isCompletelyAbove(withId(R.id.RowHeaderRecyclerView))); } @Test public void testTopLeftCorner() { mActivityTestRule.getScenario() .onActivity(activity -> { activity.setContentView(R.layout.corner_top_left); TableView tableView = activity.findViewById(R.id.tableview); CornerTestAdapter cornerTestAdapter = new CornerTestAdapter(); tableView.setAdapter(cornerTestAdapter); SimpleData simpleData = new SimpleData(5); cornerTestAdapter.setAllItems( simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); }); // Check that the corner view is created and visible // The Corner view uses cell_layout which has RelativeLayout as top item onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text // The first child of the RelativeLayout is a textView (index starts at zero) onView(allOf(withParent(withId(R.id.corner_view)), withParentIndex(0))) .check(matches(withText("Corner"))); // Check that Corner to is to the right of the column headers onView(withId(R.id.corner_view)).check(isCompletelyLeftOf(withId(R.id.ColumnHeaderRecyclerView))); // Check that Corner to is to the above of the row headers onView(withId(R.id.corner_view)).check(isCompletelyAbove(withId(R.id.RowHeaderRecyclerView))); } @Test public void testBottomLeftCorner() { mActivityTestRule.getScenario() .onActivity(activity -> { activity.setContentView(R.layout.corner_bottom_left); TableView tableView = activity.findViewById(R.id.tableview); CornerTestAdapter cornerTestAdapter = new CornerTestAdapter(); tableView.setAdapter(cornerTestAdapter); SimpleData simpleData = new SimpleData(5); cornerTestAdapter.setAllItems( simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); }); // Check that the corner view is created and visible // The Corner view uses cell_layout which has RelativeLayout as top item onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text // The first child of the RelativeLayout is a textView (index starts at zero) onView(allOf(withParent(withId(R.id.corner_view)), withParentIndex(0))) .check(matches(withText("Corner"))); // Check that Corner to is to the right of the column headers onView(withId(R.id.corner_view)).check(isCompletelyLeftOf(withId(R.id.ColumnHeaderRecyclerView))); // Check that Corner to is to the above of the row headers onView(withId(R.id.corner_view)).check(isCompletelyBelow(withId(R.id.RowHeaderRecyclerView))); } @Test public void testTopRightCorner() { mActivityTestRule.getScenario() .onActivity(activity -> { activity.setContentView(R.layout.corner_top_right); TableView tableView = activity.findViewById(R.id.tableview); CornerTestAdapter cornerTestAdapter = new CornerTestAdapter(); tableView.setAdapter(cornerTestAdapter); SimpleData simpleData = new SimpleData(5); cornerTestAdapter.setAllItems( simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); }); // Check that the corner view is created and visible // The Corner view uses cell_layout which has RelativeLayout as top item onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text // The first child of the RelativeLayout is a textView (index starts at zero) onView(allOf(withParent(withId(R.id.corner_view)), withParentIndex(0))) .check(matches(withText("Corner"))); // Check that Corner to is to the right of the column headers onView(withId(R.id.corner_view)).check(isCompletelyRightOf(withId(R.id.ColumnHeaderRecyclerView))); // Check that Corner to is to the above of the row headers onView(withId(R.id.corner_view)).check(isCompletelyAbove(withId(R.id.RowHeaderRecyclerView))); } @Test public void testBottomRightCorner() { mActivityTestRule.getScenario() .onActivity(activity -> { activity.setContentView(R.layout.corner_bottom_right); TableView tableView = activity.findViewById(R.id.tableview); CornerTestAdapter cornerTestAdapter = new CornerTestAdapter(); tableView.setAdapter(cornerTestAdapter); SimpleData simpleData = new SimpleData(5); cornerTestAdapter.setAllItems( simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); }); // Check that the corner view is created and visible // The Corner view uses cell_layout which has RelativeLayout as top item onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text // The first child of the RelativeLayout is a textView (index starts at zero) onView(allOf(withParent(withId(R.id.corner_view)), withParentIndex(0))) .check(matches(withText("Corner"))); // Check that Corner to is to the right of the column headers onView(withId(R.id.corner_view)).check(isCompletelyRightOf(withId(R.id.ColumnHeaderRecyclerView))); // Check that Corner to is to the above of the row headers onView(withId(R.id.corner_view)).check(isCompletelyBelow(withId(R.id.RowHeaderRecyclerView))); } @Test public void testCornerConstructor() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity, false); // initialize was false so set properties and call initialize tableView.setCornerViewLocation(ITableView.CornerViewLocation.BOTTOM_LEFT); tableView.initialize(); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); CornerTestAdapter cornerTestAdapter = new CornerTestAdapter(); tableView.setAdapter(cornerTestAdapter); SimpleData simpleData = new SimpleData(5); cornerTestAdapter.setAllItems( simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); activity.setContentView(rl); }); // Check that the corner view is created and visible // The Corner view uses cell_layout which has RelativeLayout as top item onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text // The first child of the RelativeLayout is a textView (index starts at zero) onView(allOf(withParent(withId(R.id.corner_view)), withParentIndex(0))) .check(matches(withText("Corner"))); // Check that Corner to is to the right of the column headers onView(withId(R.id.corner_view)).check(isCompletelyLeftOf(withId(R.id.ColumnHeaderRecyclerView))); // Check that Corner to is to the above of the row headers onView(withId(R.id.corner_view)).check(isCompletelyBelow(withId(R.id.RowHeaderRecyclerView))); } @Test public void testSetRowHeaderWidthLeft() { mActivityTestRule.getScenario() .onActivity(activity -> { activity.setContentView(R.layout.corner_top_left); TableView tableView = activity.findViewById(R.id.tableview); CornerTestAdapter cornerTestAdapter = new CornerTestAdapter(); tableView.setAdapter(cornerTestAdapter); SimpleData simpleData = new SimpleData(5); cornerTestAdapter.setAllItems( simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); // Set a new width on row header tableView.setRowHeaderWidth(200); }); // Check that the corner view is created and visible // The Corner view uses cell_layout which has RelativeLayout as top item onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text // The first child of the RelativeLayout is a textView (index starts at zero) onView(allOf(withParent(withId(R.id.corner_view)), withParentIndex(0))) .check(matches(withText("Corner"))); // Check that Corner to is to the right of the column headers onView(withId(R.id.corner_view)).check(isCompletelyLeftOf(withId(R.id.ColumnHeaderRecyclerView))); // Check that Corner to is to the above of the row headers onView(withId(R.id.corner_view)).check(isCompletelyAbove(withId(R.id.RowHeaderRecyclerView))); // Check that the corner is new width onView(withId(R.id.corner_view)).check(matches(new ViewWidthMatcher(200))); // Check that the row header is new width onView(withId(R.id.RowHeaderRecyclerView)).check(matches(new ViewWidthMatcher(200))); } @Test public void testSetRowHeaderWidthRight() { mActivityTestRule.getScenario() .onActivity(activity -> { activity.setContentView(R.layout.corner_top_right); TableView tableView = activity.findViewById(R.id.tableview); CornerTestAdapter cornerTestAdapter = new CornerTestAdapter(); tableView.setAdapter(cornerTestAdapter); SimpleData simpleData = new SimpleData(5); cornerTestAdapter.setAllItems( simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); // Set a new width on row header tableView.setRowHeaderWidth(200); }); // Check that the corner view is created and visible // The Corner view uses cell_layout which has RelativeLayout as top item onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text // The first child of the RelativeLayout is a textView (index starts at zero) onView(allOf(withParent(withId(R.id.corner_view)), withParentIndex(0))) .check(matches(withText("Corner"))); // Check that Corner to is to the right of the column headers onView(withId(R.id.corner_view)).check(isCompletelyRightOf(withId(R.id.ColumnHeaderRecyclerView))); // Check that Corner to is to the above of the row headers onView(withId(R.id.corner_view)).check(isCompletelyAbove(withId(R.id.RowHeaderRecyclerView))); // Check that the corner is new width onView(withId(R.id.corner_view)).check(matches(new ViewWidthMatcher(200))); // Check that the row header is new width onView(withId(R.id.RowHeaderRecyclerView)).check(matches(new ViewWidthMatcher(200))); } } ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/CornerViewTest.java ================================================ /* * MIT License * * Copyright (c) 2021 Andrew Beck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.test; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.Visibility; import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import android.widget.RelativeLayout; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.evrencoskun.tableview.TableView; import com.evrencoskun.tableview.test.adapters.SimpleTestAdapter; import com.evrencoskun.tableview.test.data.SimpleData; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class CornerViewTest { @Rule public ActivityScenarioRule mActivityTestRule = new ActivityScenarioRule<>(TestActivity.class); @Test public void testEmptyTable() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); SimpleData simpleData = new SimpleData(0); simpleTestAdapter.setAllItems(simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); activity.setContentView(rl); // Check that the corner view is not created (therefore not shown) Assert.assertNull(simpleTestAdapter.getCornerView()); }); } @Test public void testEmptyTableResetNonEmptyTable() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); SimpleData simpleData = new SimpleData(0); simpleTestAdapter.setAllItems(simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); activity.setContentView(rl); // Check that the corner view is not created (therefore not shown) Assert.assertNull(simpleTestAdapter.getCornerView()); // Change the items of data to reset SimpleData simpleDataReset = new SimpleData(2); simpleTestAdapter.setAllItems(simpleDataReset.getColumnHeaders(), simpleDataReset.getRowHeaders(), simpleDataReset.getCells()); // Check that the corner view is now created Assert.assertNotNull(simpleTestAdapter.getCornerView()); }); // Check the corner view is now visible onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text onView(withId(R.id.corner_text)) .check(matches(withText("Corner"))); } @Test public void testEmptyTableResetEmptyTable() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); SimpleData simpleData = new SimpleData(0); simpleTestAdapter.setAllItems(simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); activity.setContentView(rl); // Check that the corner view is not created (therefore not shown) Assert.assertNull(simpleTestAdapter.getCornerView()); // Change the items of data to reset SimpleData simpleDataReset = new SimpleData(0); simpleTestAdapter.setAllItems(simpleDataReset.getColumnHeaders(), simpleDataReset.getRowHeaders(), simpleDataReset.getCells()); // Check that the corner view is still not created Assert.assertNull(simpleTestAdapter.getCornerView()); }); } @Test public void testNonEmptyTable() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); SimpleData simpleData = new SimpleData(1); simpleTestAdapter.setAllItems(simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); activity.setContentView(rl); // Check that the corner view is created Assert.assertNotNull(simpleTestAdapter.getCornerView()); }); // Check the corner view is now visible onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text onView(withId(R.id.corner_text)) .check(matches(withText("Corner"))); } @Test public void testNonEmptyTableResetNonEmpty() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); tableView.setId(R.id.tableview); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); SimpleData simpleData = new SimpleData(1); simpleTestAdapter.setAllItems(simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); activity.setContentView(rl); // Check that the corner view is created before resetting to empty Assert.assertNotNull(simpleTestAdapter.getCornerView()); }); // Check the corner view is visible onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = activity.findViewById(R.id.tableview); SimpleTestAdapter simpleTestAdapter = (SimpleTestAdapter) tableView.getAdapter(); // Change the items of data to reset SimpleData simpleDataReset = new SimpleData(2); simpleTestAdapter.setAllItems(simpleDataReset.getColumnHeaders(), simpleDataReset.getRowHeaders(), simpleDataReset.getCells()); // Check that the corner view is still created Assert.assertNotNull(simpleTestAdapter.getCornerView()); }); // Check the corner view is still visible onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text onView(withId(R.id.corner_text)) .check(matches(withText("Corner"))); } @Test public void testNonEmptyTableResetEmpty() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); SimpleData simpleData = new SimpleData(1); simpleTestAdapter.setAllItems(simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); // Check that the corner view is created before resetting to empty Assert.assertNotNull(simpleTestAdapter.getCornerView()); // Change the items of data to reset SimpleData simpleDataReset = new SimpleData(0); simpleTestAdapter.setAllItems(simpleDataReset.getColumnHeaders(), simpleDataReset.getRowHeaders(), simpleDataReset.getCells()); activity.setContentView(rl); // Check that the corner view is still created but visibility is gone Assert.assertNotNull(simpleTestAdapter.getCornerView()); }); // Check the corner view visibility is GONE onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(Visibility.GONE))); } @Test public void testColumnHeadersOnlyTable() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); // Only want column headers SimpleData simpleData = new SimpleData(5, 0); simpleTestAdapter.setAllItems(simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); activity.setContentView(rl); // Check that the corner view is not created Assert.assertNull(simpleTestAdapter.getCornerView()); }); } @Test public void testColumnHeadersOnlyTableResetNonEmptyTable() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); // Only want column headers SimpleData simpleData = new SimpleData(5, 0); simpleTestAdapter.setAllItems(simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); // Check that the corner view is not created Assert.assertNull(simpleTestAdapter.getCornerView()); // Change the items of data to reset SimpleData simpleDataReset = new SimpleData(5); simpleTestAdapter.setAllItems(simpleDataReset.getColumnHeaders(), simpleDataReset.getRowHeaders(), simpleDataReset.getCells()); activity.setContentView(rl); // Check that the corner view is not created Assert.assertNotNull(simpleTestAdapter.getCornerView()); }); // Check the corner view is now visible onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text onView(withId(R.id.corner_text)) .check(matches(withText("Corner"))); } @Test public void testColumnHeadersOnlyTableResetEmptyTable() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); // Only want column headers SimpleData simpleData = new SimpleData(5, 0); simpleTestAdapter.setAllItems(simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); // Check that the corner view is not created Assert.assertNull(simpleTestAdapter.getCornerView()); // Change the items of data to reset SimpleData simpleDataReset = new SimpleData(0); simpleTestAdapter.setAllItems(simpleDataReset.getColumnHeaders(), simpleDataReset.getRowHeaders(), simpleDataReset.getCells()); activity.setContentView(rl); // Check that the corner view is not created Assert.assertNull(simpleTestAdapter.getCornerView()); }); } @Test public void testColumnHeadersOnlyTableShowCorner() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); // Set the option to show corner view when there is not row data tableView.setShowCornerView(true); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); // Only want column headers SimpleData simpleData = new SimpleData(5, 0); simpleTestAdapter.setAllItems(simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); activity.setContentView(rl); // Check that the corner view is created Assert.assertNotNull(simpleTestAdapter.getCornerView()); }); // Check the corner view is now visible onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text onView(withId(R.id.corner_text)) .check(matches(withText("Corner"))); } @Test public void testColumnHeadersOnlyTableShowCornerResetEmptyTable() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); // Set the option to show corner view when there is not row data tableView.setShowCornerView(true); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); // Only want column headers SimpleData simpleData = new SimpleData(5, 0); simpleTestAdapter.setAllItems(simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); // Check that the corner view is created Assert.assertNotNull(simpleTestAdapter.getCornerView()); // Change the items of data to reset SimpleData simpleDataReset = new SimpleData(0); simpleTestAdapter.setAllItems(simpleDataReset.getColumnHeaders(), simpleDataReset.getRowHeaders(), simpleDataReset.getCells()); activity.setContentView(rl); // Check that the corner view is still created Assert.assertNotNull(simpleTestAdapter.getCornerView()); }); // Check the corner view visibility is GONE onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(Visibility.GONE))); } @Test public void testColumnHeadersOnlyTableShowCornerResetNonEmptyTable() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); // Set the option to show corner view when there is not row data tableView.setShowCornerView(true); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); // Only want column headers SimpleData simpleData = new SimpleData(5, 0); simpleTestAdapter.setAllItems(simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); // Check that the corner view is created Assert.assertNotNull(simpleTestAdapter.getCornerView()); // Change the items of data to reset SimpleData simpleDataReset = new SimpleData(2); simpleTestAdapter.setAllItems(simpleDataReset.getColumnHeaders(), simpleDataReset.getRowHeaders(), simpleDataReset.getCells()); activity.setContentView(rl); // Check that the corner view is still created Assert.assertNotNull(simpleTestAdapter.getCornerView()); }); // Check the corner view visibility is VISIBLE onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); } } ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/ReverseLayoutTest.java ================================================ /* * MIT License * * Copyright (c) 2020 Andrew Beck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.test; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.PositionAssertions.isCompletelyLeftOf; import static androidx.test.espresso.assertion.PositionAssertions.isCompletelyRightOf; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.Visibility; import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withParent; import static androidx.test.espresso.matcher.ViewMatchers.withParentIndex; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; import android.widget.RelativeLayout; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.TableView; import com.evrencoskun.tableview.test.adapters.CornerTestAdapter; import com.evrencoskun.tableview.test.data.SimpleData; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class ReverseLayoutTest { @Rule public ActivityScenarioRule mActivityTestRule = new ActivityScenarioRule<>(TestActivity.class); @Test public void testDefaultLayout() { mActivityTestRule.getScenario() .onActivity(activity -> { activity.setContentView(R.layout.corner_default); TableView tableView = activity.findViewById(R.id.tableview); Assert.assertNotNull(tableView); CornerTestAdapter cornerTestAdapter = new CornerTestAdapter(); tableView.setAdapter(cornerTestAdapter); SimpleData simpleData = new SimpleData(5); cornerTestAdapter.setAllItems( simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); }); // Check that the corner view is created and visible // The Corner view uses cell_layout which has RelativeLayout as top item onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text // The first child of the RelativeLayout is a textView (index starts at zero) onView(allOf(withParent(withId(R.id.corner_view)), withParentIndex(0))) .check(matches(withText("Corner"))); // Check that column headers are ordered Left to Right onView(withText("c:0")).check(isCompletelyLeftOf(withText("c:1"))); // Check that first cell data row are ordered Left to Right onView(withText("r:0c:0")).check(isCompletelyLeftOf(withText("r:0c:1"))); } @Test public void testReverseLayout() { mActivityTestRule.getScenario() .onActivity(activity -> { activity.setContentView(R.layout.reverse_layout); TableView tableView = activity.findViewById(R.id.tableview); Assert.assertNotNull(tableView); CornerTestAdapter cornerTestAdapter = new CornerTestAdapter(); tableView.setAdapter(cornerTestAdapter); SimpleData simpleData = new SimpleData(5); cornerTestAdapter.setAllItems( simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); }); // Check that the corner view is created and visible // The Corner view uses cell_layout which has RelativeLayout as top item onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text // The first child of the RelativeLayout is a textView (index starts at zero) onView(allOf(withParent(withId(R.id.corner_view)), withParentIndex(0))) .check(matches(withText("Corner"))); // Check that column headers are ordered Right to Left onView(withText("c:0")).check(isCompletelyRightOf(withText("c:1"))); // Check that first cell data row are ordered Right to Left onView(withText("r:0c:0")).check(isCompletelyRightOf(withText("r:0c:1"))); } @Test public void testReverseConstructor() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity, false); // initialize was false so set properties and call initialize //Set CornerView to Top Right and ReverseLayout = true tableView.setCornerViewLocation(ITableView.CornerViewLocation.TOP_RIGHT); tableView.setReverseLayout(true); tableView.initialize(); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); CornerTestAdapter cornerTestAdapter = new CornerTestAdapter(); tableView.setAdapter(cornerTestAdapter); SimpleData simpleData = new SimpleData(5); cornerTestAdapter.setAllItems( simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); activity.setContentView(rl); }); // Check that the corner view is created and visible // The Corner view uses cell_layout which has RelativeLayout as top item onView(withId(R.id.corner_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); // Check that it is the expected corner view by checking the text // The first child of the RelativeLayout is a textView (index starts at zero) onView(allOf(withParent(withId(R.id.corner_view)), withParentIndex(0))) .check(matches(withText("Corner"))); // Check that column headers are ordered Right to Left onView(withText("c:0")).check(isCompletelyRightOf(withText("c:1"))); // Check that first cell data row are ordered Right to Left onView(withText("r:0c:0")).check(isCompletelyRightOf(withText("r:0c:1"))); } } ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/SimpleActivityTest.java ================================================ /* * MIT License * * Copyright (c) 2021 Andrew Beck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.test; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withParent; import static androidx.test.espresso.matcher.ViewMatchers.withParentIndex; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; import android.view.View; import android.widget.RelativeLayout; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import com.evrencoskun.tableview.TableView; import com.evrencoskun.tableview.test.adapters.SimpleTestAdapter; import com.evrencoskun.tableview.test.data.SimpleData; import org.hamcrest.Matcher; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class SimpleActivityTest { @Rule public ActivityScenarioRule mActivityTestRule = new ActivityScenarioRule<>(TestActivity.class); @Test public void testDefaults() { TableView tableView = new TableView(InstrumentationRegistry.getInstrumentation().getTargetContext()); Assert.assertFalse(tableView.isAllowClickInsideCell()); Assert.assertTrue(tableView.isShowHorizontalSeparators()); Assert.assertTrue(tableView.isShowVerticalSeparators()); } @Test public void testSmallTable() { mActivityTestRule.getScenario() .onActivity(activity -> { TableView tableView = new TableView(activity); tableView.setId(R.id.tableview); RelativeLayout rl = new RelativeLayout(activity); rl.addView(tableView); SimpleTestAdapter simpleTestAdapter = new SimpleTestAdapter(); tableView.setAdapter(simpleTestAdapter); SimpleData simpleData = new SimpleData(5); simpleTestAdapter.setAllItems( simpleData.getColumnHeaders(), simpleData.getRowHeaders(), simpleData.getCells()); activity.setContentView(rl); }); // Check that the row header was created as expected at 5th Row (index starts at zero) // cell_layout has LinearLayout as top item Matcher rowHeaders = allOf(withParent(withId(R.id.tableview)), withParentIndex(1)); Matcher rowHeader = allOf(withParent(rowHeaders), withParentIndex(4)); onView(allOf(withParent(rowHeader), withParentIndex(0))) .check(matches(withText("r:4"))); // Check that the column header was created as expected at 5th Row (index starts at zero) // cell_layout has LinearLayout as top item Matcher columnHeaders = allOf(withParent(withId(R.id.tableview)), withParentIndex(0)); Matcher columnHeader = allOf(withParent(columnHeaders), withParentIndex(4)); onView(allOf(withParent(columnHeader), withParentIndex(0))) .check(matches(withText("c:4"))); } } ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/TestActivity.java ================================================ /* * MIT License * * Copyright (c) 2021 Andrew Beck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.test; import android.app.Activity; public class TestActivity extends Activity { } ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/adapters/AbstractTableAdapterTest.java ================================================ package com.evrencoskun.tableview.test.adapters; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static java.util.Collections.emptyList; import android.view.View; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import com.evrencoskun.tableview.TableView; import com.evrencoskun.tableview.test.TestActivity; import com.evrencoskun.tableview.test.data.SimpleData; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class AbstractTableAdapterTest { @Rule public ActivityScenarioRule mActivityTestRule = new ActivityScenarioRule<>(TestActivity.class); private SimpleData mData; private TableView mTableView; private SimpleTestAdapter mAdapter; @Before public void before() { mData = new SimpleData(5); mTableView = new TableView(InstrumentationRegistry.getInstrumentation().getContext()); mAdapter = new SimpleTestAdapter(); mAdapter.setTableView(mTableView); } @Test public void testCornerViewStateWithDisabledCorner() { mTableView.setShowCornerView(false); assertNull(mAdapter.getCornerView()); mAdapter.setAllItems(null, null, null); assertNull(mAdapter.getCornerView()); mAdapter.setAllItems(mData.getColumnHeaders(), mData.getRowHeaders(), mData.getCells()); assertNotNull(mAdapter.getCornerView()); assertEquals(View.VISIBLE, mAdapter.getCornerView().getVisibility()); mAdapter.setAllItems(emptyList(), emptyList(), emptyList()); assertNotNull(mAdapter.getCornerView()); assertEquals(View.GONE, mAdapter.getCornerView().getVisibility()); } @Test public void testCornerViewStateWithEnabledCorners() { mTableView.setShowCornerView(true); assertNull(mAdapter.getCornerView()); mAdapter.setAllItems(mData.getColumnHeaders(), mData.getRowHeaders(), mData.getCells()); assertNotNull(mAdapter.getCornerView()); assertEquals(View.VISIBLE, mAdapter.getCornerView().getVisibility()); mAdapter.setAllItems(null, null, null); assertNotNull(mAdapter.getCornerView()); assertEquals(View.GONE, mAdapter.getCornerView().getVisibility()); // We set some data, that we then reset to empty mAdapter.setAllItems(mData.getColumnHeaders(), mData.getRowHeaders(), mData.getCells()); mAdapter.setAllItems(emptyList(), emptyList(), emptyList()); assertNotNull(mAdapter.getCornerView()); assertEquals(View.GONE, mAdapter.getCornerView().getVisibility()); mAdapter.setAllItems(mData.getColumnHeaders(), null, null); assertNotNull(mAdapter.getCornerView()); assertEquals(View.VISIBLE, mAdapter.getCornerView().getVisibility()); mAdapter.setAllItems(null, mData.getRowHeaders(), null); assertNotNull(mAdapter.getCornerView()); assertEquals(View.GONE, mAdapter.getCornerView().getVisibility()); mAdapter.setAllItems(mData.getColumnHeaders(), mData.getRowHeaders(), null); assertNotNull(mAdapter.getCornerView()); assertEquals(View.VISIBLE, mAdapter.getCornerView().getVisibility()); } @Test public void testCornerViewStateWithToggledCorners() { mTableView.setShowCornerView(true); assertNull(mAdapter.getCornerView()); mAdapter.setAllItems(mData.getColumnHeaders(), mData.getRowHeaders(), mData.getCells()); assertNotNull(mAdapter.getCornerView()); assertEquals(View.VISIBLE, mAdapter.getCornerView().getVisibility()); mTableView.setShowCornerView(false); mAdapter.setAllItems(mData.getColumnHeaders(), mData.getRowHeaders(), mData.getCells()); assertNotNull(mAdapter.getCornerView()); assertEquals(View.VISIBLE, mAdapter.getCornerView().getVisibility()); mAdapter.setAllItems(null, null, null); assertNotNull(mAdapter.getCornerView()); assertEquals(View.GONE, mAdapter.getCornerView().getVisibility()); // We set some data, that we then reset to empty mAdapter.setAllItems(mData.getColumnHeaders(), mData.getRowHeaders(), mData.getCells()); mAdapter.setAllItems(emptyList(), emptyList(), emptyList()); assertNotNull(mAdapter.getCornerView()); assertEquals(View.GONE, mAdapter.getCornerView().getVisibility()); mAdapter.setAllItems(mData.getColumnHeaders(), null, null); assertNotNull(mAdapter.getCornerView()); assertEquals(View.GONE, mAdapter.getCornerView().getVisibility()); mAdapter.setAllItems(null, mData.getRowHeaders(), null); assertNotNull(mAdapter.getCornerView()); assertEquals(View.GONE, mAdapter.getCornerView().getVisibility()); mAdapter.setAllItems(mData.getColumnHeaders(), mData.getRowHeaders(), null); assertNotNull(mAdapter.getCornerView()); assertEquals(View.VISIBLE, mAdapter.getCornerView().getVisibility()); } } ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/adapters/CornerTestAdapter.java ================================================ /* * MIT License * * Copyright (c) 2021 Andrew Beck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.test.adapters; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import com.evrencoskun.tableview.test.R; import com.evrencoskun.tableview.adapter.AbstractTableAdapter; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.test.models.Cell; import com.evrencoskun.tableview.test.models.ColumnHeader; import com.evrencoskun.tableview.test.models.RowHeader; public class CornerTestAdapter extends AbstractTableAdapter { static class TestCellViewHolder extends AbstractViewHolder { final LinearLayout cell_container; final TextView cell_textview; TestCellViewHolder(View itemView) { super(itemView); cell_container = itemView.findViewById(com.evrencoskun.tableview.test.R.id.cell_container); cell_textview = itemView.findViewById(com.evrencoskun.tableview.test.R.id.cell_data); } } @NonNull public AbstractViewHolder onCreateCellViewHolder(@NonNull ViewGroup parent, int viewType) { View layout = LayoutInflater.from(parent.getContext()) .inflate(R.layout.cell_layout, parent, false); return new TestCellViewHolder(layout); } public void onBindCellViewHolder(@NonNull AbstractViewHolder holder, Cell cell, int columnPosition, int rowPosition) { TestCellViewHolder viewHolder = (TestCellViewHolder) holder; viewHolder.cell_textview.setText(cell.getData() != null ? cell.getData().toString() : ""); viewHolder.cell_container.getLayoutParams().width = LinearLayout.LayoutParams.WRAP_CONTENT; viewHolder.cell_textview.requestLayout(); } static class TestColumnHeaderViewHolder extends AbstractViewHolder { final LinearLayout column_header_container; final TextView cell_textview; public TestColumnHeaderViewHolder(View itemView) { super(itemView); column_header_container = itemView.findViewById(R.id.column_header_container); cell_textview = itemView.findViewById(R.id.column_header_textView); } } @NonNull public AbstractViewHolder onCreateColumnHeaderViewHolder(@NonNull ViewGroup parent, int viewType) { View layout = LayoutInflater.from(parent.getContext()) .inflate(R.layout.column_layout, parent, false); return new TestColumnHeaderViewHolder(layout); } public void onBindColumnHeaderViewHolder(@NonNull AbstractViewHolder holder, ColumnHeader columnHeader, int position) { TestColumnHeaderViewHolder viewHolder = (TestColumnHeaderViewHolder) holder; if (columnHeader.getData() != null) viewHolder.cell_textview.setText(columnHeader.getData().toString()); viewHolder.column_header_container.getLayoutParams().width = LinearLayout.LayoutParams.WRAP_CONTENT; viewHolder.cell_textview.requestLayout(); } static class TestRowHeaderViewHolder extends AbstractViewHolder { final TextView cell_textview; public TestRowHeaderViewHolder(View itemView) { super(itemView); cell_textview = itemView.findViewById(R.id.row_header_textView); } } @NonNull public AbstractViewHolder onCreateRowHeaderViewHolder(@NonNull ViewGroup parent, int viewType) { View layout = LayoutInflater.from(parent.getContext()) .inflate(R.layout.row_layout, parent, false); return new TestRowHeaderViewHolder(layout); } public void onBindRowHeaderViewHolder(@NonNull AbstractViewHolder holder, RowHeader rowHeader, int position) { TestRowHeaderViewHolder viewHolder = (TestRowHeaderViewHolder) holder; if (rowHeader.getData() != null) viewHolder.cell_textview.setText(rowHeader.getData().toString()); } @NonNull public View onCreateCornerView(@NonNull ViewGroup parent) { return LayoutInflater.from(parent.getContext()) .inflate(R.layout.corner_layout, parent, false); } } ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/adapters/SimpleTestAdapter.java ================================================ /* * MIT License * * Copyright (c) 2021 Andrew Beck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.test.adapters; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import com.evrencoskun.tableview.test.R; import com.evrencoskun.tableview.adapter.AbstractTableAdapter; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.test.models.Cell; import com.evrencoskun.tableview.test.models.ColumnHeader; import com.evrencoskun.tableview.test.models.RowHeader; public class SimpleTestAdapter extends AbstractTableAdapter { static class TestCellViewHolder extends AbstractViewHolder { final LinearLayout cell_container; final TextView cell_textview; TestCellViewHolder(View itemView) { super(itemView); cell_container = itemView.findViewById(com.evrencoskun.tableview.test.R.id.cell_container); cell_textview = itemView.findViewById(com.evrencoskun.tableview.test.R.id.cell_data); } } @NonNull public AbstractViewHolder onCreateCellViewHolder(@NonNull ViewGroup parent, int viewType) { View layout = LayoutInflater.from(parent.getContext()) .inflate(R.layout.cell_layout, parent, false); return new TestCellViewHolder(layout); } public void onBindCellViewHolder(@NonNull AbstractViewHolder holder, Cell cell, int columnPosition, int rowPosition) { TestCellViewHolder viewHolder = (TestCellViewHolder) holder; viewHolder.cell_textview.setText(cell.getData() != null ? cell.getData().toString() : ""); viewHolder.cell_container.getLayoutParams().width = LinearLayout.LayoutParams.WRAP_CONTENT; viewHolder.cell_textview.requestLayout(); } @NonNull public AbstractViewHolder onCreateColumnHeaderViewHolder(@NonNull ViewGroup parent, int viewType) { View layout = LayoutInflater.from(parent.getContext()) .inflate(R.layout.cell_layout, parent, false); return new TestCellViewHolder(layout); } public void onBindColumnHeaderViewHolder(@NonNull AbstractViewHolder holder, ColumnHeader columnHeader, int position) { TestCellViewHolder viewHolder = (TestCellViewHolder) holder; if (columnHeader.getData() != null) viewHolder.cell_textview.setText(columnHeader.getData().toString()); viewHolder.cell_container.getLayoutParams().width = LinearLayout.LayoutParams.WRAP_CONTENT; viewHolder.cell_textview.requestLayout(); } @NonNull public AbstractViewHolder onCreateRowHeaderViewHolder(@NonNull ViewGroup parent, int viewType) { View layout = LayoutInflater.from(parent.getContext()) .inflate(R.layout.cell_layout, parent, false); return new TestCellViewHolder(layout); } public void onBindRowHeaderViewHolder(@NonNull AbstractViewHolder holder, RowHeader rowHeader, int position) { TestCellViewHolder viewHolder = (TestCellViewHolder) holder; if (rowHeader.getData() != null) viewHolder.cell_textview.setText(rowHeader.getData().toString()); } @NonNull public View onCreateCornerView(@NonNull ViewGroup parent) { return LayoutInflater.from(parent.getContext()) .inflate(R.layout.corner_layout, parent, false); } } ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/data/SimpleData.java ================================================ /* * MIT License * * Copyright (c) 2021 Andrew Beck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.test.data; import com.evrencoskun.tableview.test.models.Cell; import com.evrencoskun.tableview.test.models.ColumnHeader; import com.evrencoskun.tableview.test.models.RowHeader; import java.util.ArrayList; import java.util.List; public class SimpleData { private List> cells; private List columnHeaders; private List rowHeaders; public SimpleData(int size){ init(size, size); } public SimpleData(int columnSize, int rowSize) { init(columnSize, rowSize); } private void init(int columnSize, int rowSize) { rowHeaders = new ArrayList<>(); for (int i = 0; i < rowSize; i++) { rowHeaders.add(new RowHeader(String.valueOf(i), "r:" + i)); } columnHeaders = new ArrayList<>(); for (int i = 0; i < columnSize; i++) { columnHeaders.add(new ColumnHeader(String.valueOf(i), "c:" + i)); } cells = new ArrayList<>(); for (int i = 0; i < rowSize; i++) { ArrayList cellList = new ArrayList<>(); for (int j = 0; j < columnSize; j++) { String id = j + ":" + i; cellList.add(new Cell(id, "r:" + i + "c:" + j)); } cells.add(cellList); } } public List> getCells() { return cells; } public void setCells(List> cells) { this.cells = cells; } public List getColumnHeaders() { return columnHeaders; } public void setColumnHeaders(List columnHeaders) { this.columnHeaders = columnHeaders; } public List getRowHeaders() { return rowHeaders; } public void setRowHeaders(List rowHeaders) { this.rowHeaders = rowHeaders; } } ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/matchers/ViewWidthMatcher.java ================================================ /* * MIT License * * Copyright (c) 2021 Andrew Beck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.test.matchers; import android.view.View; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; public class ViewWidthMatcher extends TypeSafeMatcher { private final int expectedWidth; public ViewWidthMatcher(int expectedWidth) { super(View.class); this.expectedWidth = expectedWidth; } @Override protected boolean matchesSafely(View target) { int targetWidth = target.getWidth(); return targetWidth == expectedWidth; } @Override public void describeTo(Description description) { description.appendText("with WidthMatcher: "); description.appendValue(expectedWidth); } } ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/models/Cell.java ================================================ /* * MIT License * * Copyright (c) 2021 Andrew Beck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.test.models; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.evrencoskun.tableview.filter.IFilterableModel; import com.evrencoskun.tableview.sort.ISortableModel; public class Cell implements ISortableModel, IFilterableModel { @NonNull private String mId; @Nullable private Object mData; @NonNull private String mFilterKeyword; public Cell(@NonNull String id, @Nullable Object data) { this.mId = id; this.mData = data; this.mFilterKeyword = String.valueOf(data); } /** * This is necessary for sorting process. * See ISortableModel */ @NonNull @Override public String getId() { return mId; } /** * This is necessary for sorting process. * See ISortableModel */ @Nullable @Override public Object getContent() { return mData; } @Nullable public Object getData() { return mData; } public void setData(@Nullable Object data) { mData = data; } @NonNull @Override public String getFilterableKeyword() { return mFilterKeyword; } } ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/models/ColumnHeader.java ================================================ /* * MIT License * * Copyright (c) 2021 Andrew Beck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.test.models; import androidx.annotation.NonNull; import androidx.annotation.Nullable; public class ColumnHeader extends Cell { public ColumnHeader(@NonNull String id, @Nullable String data) { super(id, data); } } ================================================ FILE: tableview/src/androidTest/java/com/evrencoskun/tableview/test/models/RowHeader.java ================================================ /* * MIT License * * Copyright (c) 2021 Andrew Beck * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.test.models; import androidx.annotation.NonNull; import androidx.annotation.Nullable; public class RowHeader extends Cell { public RowHeader(@NonNull String id, @Nullable String data) { super(id, data); } } ================================================ FILE: tableview/src/androidTest/res/layout/cell_layout.xml ================================================ ================================================ FILE: tableview/src/androidTest/res/layout/column_layout.xml ================================================ ================================================ FILE: tableview/src/androidTest/res/layout/corner_bottom_left.xml ================================================ ================================================ FILE: tableview/src/androidTest/res/layout/corner_bottom_right.xml ================================================ ================================================ FILE: tableview/src/androidTest/res/layout/corner_default.xml ================================================ ================================================ FILE: tableview/src/androidTest/res/layout/corner_layout.xml ================================================ ================================================ FILE: tableview/src/androidTest/res/layout/corner_top_left.xml ================================================ ================================================ FILE: tableview/src/androidTest/res/layout/corner_top_right.xml ================================================ ================================================ FILE: tableview/src/androidTest/res/layout/reverse_layout.xml ================================================ ================================================ FILE: tableview/src/androidTest/res/layout/row_layout.xml ================================================ ================================================ FILE: tableview/src/androidTest/res/values/colors.xml ================================================ #ffffff #0a0a0a #0a0a0a ================================================ FILE: tableview/src/androidTest/res/values/dimens.xml ================================================ 40dp 12sp 55dp 40dp ================================================ FILE: tableview/src/main/AndroidManifest.xml ================================================ ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/ITableView.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview; import android.content.Context; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import com.evrencoskun.tableview.adapter.AbstractTableAdapter; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerView; import com.evrencoskun.tableview.filter.Filter; import com.evrencoskun.tableview.handler.ColumnSortHandler; import com.evrencoskun.tableview.handler.FilterHandler; import com.evrencoskun.tableview.handler.ScrollHandler; import com.evrencoskun.tableview.handler.SelectionHandler; import com.evrencoskun.tableview.handler.VisibilityHandler; import com.evrencoskun.tableview.layoutmanager.CellLayoutManager; import com.evrencoskun.tableview.layoutmanager.ColumnHeaderLayoutManager; import com.evrencoskun.tableview.listener.ITableViewListener; import com.evrencoskun.tableview.listener.scroll.HorizontalRecyclerViewListener; import com.evrencoskun.tableview.listener.scroll.VerticalRecyclerViewListener; import com.evrencoskun.tableview.sort.SortState; /** * Created by evrencoskun on 19/06/2017. */ public interface ITableView { void addView(View child, ViewGroup.LayoutParams params); boolean hasFixedWidth(); boolean isIgnoreSelectionColors(); boolean isShowHorizontalSeparators(); boolean isShowVerticalSeparators(); boolean isAllowClickInsideCell(); boolean isSortable(); @NonNull Context getContext(); @NonNull CellRecyclerView getCellRecyclerView(); @NonNull CellRecyclerView getColumnHeaderRecyclerView(); @NonNull CellRecyclerView getRowHeaderRecyclerView(); @NonNull ColumnHeaderLayoutManager getColumnHeaderLayoutManager(); @NonNull CellLayoutManager getCellLayoutManager(); @NonNull LinearLayoutManager getRowHeaderLayoutManager(); @NonNull HorizontalRecyclerViewListener getHorizontalRecyclerViewListener(); @NonNull VerticalRecyclerViewListener getVerticalRecyclerViewListener(); @Nullable ITableViewListener getTableViewListener(); @NonNull SelectionHandler getSelectionHandler(); @Nullable ColumnSortHandler getColumnSortHandler(); @NonNull VisibilityHandler getVisibilityHandler(); @NonNull DividerItemDecoration getHorizontalItemDecoration(); @NonNull DividerItemDecoration getVerticalItemDecoration(); @NonNull SortState getSortingStatus(int column); @Nullable SortState getRowHeaderSortingStatus(); void scrollToColumnPosition(int column); void scrollToColumnPosition(int column, int offset); void scrollToRowPosition(int row); void scrollToRowPosition(int row, int offset); void showRow(int row); void hideRow(int row); boolean isRowVisible(int row); void showAllHiddenRows(); void clearHiddenRowList(); void showColumn(int column); void hideColumn(int column); boolean isColumnVisible(int column); void showAllHiddenColumns(); void clearHiddenColumnList(); int getShadowColor(); int getSelectedColor(); int getUnSelectedColor(); int getSeparatorColor(); void sortColumn(int columnPosition, @NonNull SortState sortState); void sortRowHeader(@NonNull SortState sortState); void remeasureColumnWidth(int column); int getRowHeaderWidth(); void setRowHeaderWidth(int rowHeaderWidth); boolean getShowCornerView(); enum CornerViewLocation { TOP_LEFT(0), TOP_RIGHT(1), BOTTOM_LEFT(2), BOTTOM_RIGHT(3); int id; CornerViewLocation(int id) { this.id = id; } static CornerViewLocation fromId(int id) { for (CornerViewLocation c : values()) { if (c.id == id) return c; } // If enum not found return default of Top Left return TOP_LEFT; } } CornerViewLocation getCornerViewLocation(); void setCornerViewLocation(CornerViewLocation cornerViewLocation); int getGravity(); boolean getReverseLayout(); void setReverseLayout(boolean reverseLayout); @Nullable AbstractTableAdapter getAdapter(); /** * Filters the whole table using the provided Filter object which supports multiple filters. * * @param filter The filter object. */ void filter(@NonNull Filter filter); /** * Retrieves the FilterHandler of the TableView. * * @return The FilterHandler of the TableView. */ @Nullable FilterHandler getFilterHandler(); /** * Retrieves the ScrollHandler of the TableView. * * @return The ScrollHandler of the TableView. */ @NonNull ScrollHandler getScrollHandler(); } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/TableView.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview; import android.content.Context; import android.content.res.TypedArray; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Parcelable; import android.util.AttributeSet; import android.view.Gravity; import android.view.ViewGroup; import android.widget.FrameLayout; import com.evrencoskun.tableview.adapter.AbstractTableAdapter; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerView; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.filter.Filter; import com.evrencoskun.tableview.handler.ColumnSortHandler; import com.evrencoskun.tableview.handler.ColumnWidthHandler; import com.evrencoskun.tableview.handler.FilterHandler; import com.evrencoskun.tableview.handler.PreferencesHandler; import com.evrencoskun.tableview.handler.ScrollHandler; import com.evrencoskun.tableview.handler.SelectionHandler; import com.evrencoskun.tableview.handler.VisibilityHandler; import com.evrencoskun.tableview.layoutmanager.CellLayoutManager; import com.evrencoskun.tableview.layoutmanager.ColumnHeaderLayoutManager; import com.evrencoskun.tableview.listener.ITableViewListener; import com.evrencoskun.tableview.listener.TableViewLayoutChangeListener; import com.evrencoskun.tableview.listener.itemclick.ColumnHeaderRecyclerViewItemClickListener; import com.evrencoskun.tableview.listener.itemclick.RowHeaderRecyclerViewItemClickListener; import com.evrencoskun.tableview.listener.scroll.HorizontalRecyclerViewListener; import com.evrencoskun.tableview.listener.scroll.VerticalRecyclerViewListener; import com.evrencoskun.tableview.preference.SavedState; import com.evrencoskun.tableview.sort.SortState; import androidx.annotation.AttrRes; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; /** * Created by evrencoskun on 11/06/2017. */ public class TableView extends FrameLayout implements ITableView { @NonNull protected CellRecyclerView mCellRecyclerView; @NonNull protected CellRecyclerView mColumnHeaderRecyclerView; @NonNull protected CellRecyclerView mRowHeaderRecyclerView; @Nullable protected AbstractTableAdapter mTableAdapter; @Nullable private ITableViewListener mTableViewListener; @NonNull private VerticalRecyclerViewListener mVerticalRecyclerListener; @NonNull private HorizontalRecyclerViewListener mHorizontalRecyclerViewListener; @NonNull private ColumnHeaderLayoutManager mColumnHeaderLayoutManager; @NonNull private LinearLayoutManager mRowHeaderLayoutManager; @NonNull private CellLayoutManager mCellLayoutManager; @NonNull private DividerItemDecoration mVerticalItemDecoration; @NonNull private DividerItemDecoration mHorizontalItemDecoration; @NonNull private SelectionHandler mSelectionHandler; @Nullable private ColumnSortHandler mColumnSortHandler; @NonNull private VisibilityHandler mVisibilityHandler; @NonNull private ScrollHandler mScrollHandler; @Nullable private FilterHandler mFilterHandler; @NonNull private PreferencesHandler mPreferencesHandler; @NonNull private ColumnWidthHandler mColumnWidthHandler; private int mRowHeaderWidth; private int mColumnHeaderHeight; private int mSelectedColor; private int mUnSelectedColor; private int mShadowColor; private int mSeparatorColor = -1; private boolean mHasFixedWidth; private boolean mIgnoreSelectionColors; private boolean mShowHorizontalSeparators = true; private boolean mShowVerticalSeparators = true; private boolean mAllowClickInsideCell = false; private boolean mAllowClickInsideRowHeader = false; private boolean mAllowClickInsideColumnHeader = false; private boolean mIsSortable; private boolean mShowCornerView = false; private CornerViewLocation mCornerViewLocation; private boolean mReverseLayout = false; public TableView(@NonNull Context context) { super(context); initialDefaultValues(null); initialize(); } public TableView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); initialDefaultValues(attrs); initialize(); } public TableView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); initialDefaultValues(null); initialize(); } /** * Two Part class construction
* Allows you to set various properties before class initialization if {@code intialize = false}
* Allowing more control when programmically creating the class * * @param context * @param initialize {@code false} to not call second part of class construction * *

Note: If initialize is false you need to call {@code initilize()} method yourself. * */ public TableView(@NonNull Context context, boolean initialize) { super(context); initialDefaultValues(null); if (initialize) initialize(); } private void initialDefaultValues(@Nullable AttributeSet attrs) { // Dimensions mRowHeaderWidth = (int) getResources().getDimension(R.dimen.default_row_header_width); mColumnHeaderHeight = (int) getResources().getDimension(R.dimen .default_column_header_height); // Cornerview location mCornerViewLocation = ITableView.CornerViewLocation.TOP_LEFT; // Reverse Layout mReverseLayout = false; // Colors mSelectedColor = ContextCompat.getColor(getContext(), R.color .table_view_default_selected_background_color); mUnSelectedColor = ContextCompat.getColor(getContext(), R.color .table_view_default_unselected_background_color); mShadowColor = ContextCompat.getColor(getContext(), R.color .table_view_default_shadow_background_color); if (attrs == null) { // That means TableView is created programmatically. return; } // Get values from xml attributes TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable .TableView, 0, 0); try { // Dimensions mRowHeaderWidth = (int) a.getDimension(R.styleable.TableView_row_header_width, mRowHeaderWidth); mColumnHeaderHeight = (int) a.getDimension(R.styleable .TableView_column_header_height, mColumnHeaderHeight); // CornerView location mCornerViewLocation = CornerViewLocation.fromId(a.getInt(R.styleable.TableView_corner_view_location, 0)); // Reverse Layout mReverseLayout = a.getBoolean(R.styleable.TableView_reverse_layout, mReverseLayout); // Colors mSelectedColor = a.getColor(R.styleable.TableView_selected_color, mSelectedColor); mUnSelectedColor = a.getColor(R.styleable.TableView_unselected_color, mUnSelectedColor); mShadowColor = a.getColor(R.styleable.TableView_shadow_color, mShadowColor); mSeparatorColor = a.getColor(R.styleable.TableView_separator_color, ContextCompat .getColor(getContext(), R.color.table_view_default_separator_color)); // Booleans mShowVerticalSeparators = a.getBoolean(R.styleable.TableView_show_vertical_separator, mShowVerticalSeparators); mShowHorizontalSeparators = a.getBoolean(R.styleable .TableView_show_horizontal_separator, mShowHorizontalSeparators); mAllowClickInsideCell = a.getBoolean(R.styleable.TableView_allow_click_inside_cell, mAllowClickInsideCell); mAllowClickInsideRowHeader = a.getBoolean(R.styleable.TableView_allow_click_inside_row_header, mAllowClickInsideRowHeader); mAllowClickInsideColumnHeader = a.getBoolean(R.styleable.TableView_allow_click_inside_column_header, mAllowClickInsideColumnHeader); } finally { a.recycle(); } } /** * Second Part of class construction * *

Note: This should only be called directly if the class was constructed * with initialize boolean set to {@code false} */ public void initialize() { // Create Views mColumnHeaderRecyclerView = createColumnHeaderRecyclerView(); mRowHeaderRecyclerView = createRowHeaderRecyclerView(); mCellRecyclerView = createCellRecyclerView(); // Set some Id to help in identification mColumnHeaderRecyclerView.setId(R.id.ColumnHeaderRecyclerView); mRowHeaderRecyclerView.setId(R.id.RowHeaderRecyclerView); mCellRecyclerView.setId(R.id.CellRecyclerView); // Add Views addView(mColumnHeaderRecyclerView); addView(mRowHeaderRecyclerView); addView(mCellRecyclerView); // Create Handlers mSelectionHandler = new SelectionHandler(this); mVisibilityHandler = new VisibilityHandler(this); mScrollHandler = new ScrollHandler(this); mPreferencesHandler = new PreferencesHandler(this); mColumnWidthHandler = new ColumnWidthHandler(this); initializeListeners(); } protected void initializeListeners() { // --- Listeners to help Scroll synchronously --- // It handles Vertical scroll listener mVerticalRecyclerListener = new VerticalRecyclerViewListener(this); // Set this listener both of Cell RecyclerView and RowHeader RecyclerView mRowHeaderRecyclerView.addOnItemTouchListener(mVerticalRecyclerListener); mCellRecyclerView.addOnItemTouchListener(mVerticalRecyclerListener); // It handles Horizontal scroll listener mHorizontalRecyclerViewListener = new HorizontalRecyclerViewListener(this); // Set scroll listener to be able to scroll all rows synchrony. mColumnHeaderRecyclerView.addOnItemTouchListener(mHorizontalRecyclerViewListener); // --- Listeners to help item clicks --- // Create item click listeners // Add item click listener for column header recyclerView if (mAllowClickInsideColumnHeader) { ColumnHeaderRecyclerViewItemClickListener columnHeaderRecyclerViewItemClickListener = new ColumnHeaderRecyclerViewItemClickListener (mColumnHeaderRecyclerView, this); mColumnHeaderRecyclerView.addOnItemTouchListener(columnHeaderRecyclerViewItemClickListener); } // Add item click listener for row header recyclerView if (mAllowClickInsideRowHeader) { RowHeaderRecyclerViewItemClickListener rowHeaderRecyclerViewItemClickListener = new RowHeaderRecyclerViewItemClickListener (mRowHeaderRecyclerView, this); mRowHeaderRecyclerView.addOnItemTouchListener(rowHeaderRecyclerViewItemClickListener); } // Add Layout change listener both of Column Header & Cell recyclerView to detect // changing size // For some case, it is pretty necessary. TableViewLayoutChangeListener layoutChangeListener = new TableViewLayoutChangeListener (this); mColumnHeaderRecyclerView.addOnLayoutChangeListener(layoutChangeListener); mCellRecyclerView.addOnLayoutChangeListener(layoutChangeListener); } @NonNull protected CellRecyclerView createColumnHeaderRecyclerView() { CellRecyclerView recyclerView = new CellRecyclerView(getContext()); // Set layout manager recyclerView.setLayoutManager(getColumnHeaderLayoutManager()); // Set layout params LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, mColumnHeaderHeight, getGravity()); // If the corner is on the right the margin needs to be on the right if (mCornerViewLocation == CornerViewLocation.TOP_RIGHT || mCornerViewLocation == CornerViewLocation.BOTTOM_RIGHT) { layoutParams.rightMargin = mRowHeaderWidth; } else { layoutParams.leftMargin = mRowHeaderWidth; } recyclerView.setLayoutParams(layoutParams); if (isShowHorizontalSeparators()) { // Add vertical item decoration to display column line recyclerView.addItemDecoration(getHorizontalItemDecoration()); } return recyclerView; } @NonNull protected CellRecyclerView createRowHeaderRecyclerView() { CellRecyclerView recyclerView = new CellRecyclerView(getContext()); // Set layout manager recyclerView.setLayoutManager(getRowHeaderLayoutManager()); // Set layout params LayoutParams layoutParams = new LayoutParams(mRowHeaderWidth, LayoutParams.WRAP_CONTENT, getGravity()); // If the corner is on the bottom the margin needs to be on the bottom if (mCornerViewLocation == CornerViewLocation.BOTTOM_LEFT || mCornerViewLocation == CornerViewLocation.BOTTOM_RIGHT) { layoutParams.bottomMargin = mColumnHeaderHeight; } else { layoutParams.topMargin = mColumnHeaderHeight; } recyclerView.setLayoutParams(layoutParams); if (isShowVerticalSeparators()) { // Add vertical item decoration to display row line recyclerView.addItemDecoration(getVerticalItemDecoration()); } return recyclerView; } @NonNull protected CellRecyclerView createCellRecyclerView() { CellRecyclerView recyclerView = new CellRecyclerView(getContext()); // Disable multitouch recyclerView.setMotionEventSplittingEnabled(false); // Set layout manager recyclerView.setLayoutManager(getCellLayoutManager()); // Set layout params LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams .WRAP_CONTENT, getGravity()); // If the corner is on the right the margin needs to be on the right if (mCornerViewLocation == CornerViewLocation.TOP_RIGHT || mCornerViewLocation == CornerViewLocation.BOTTOM_RIGHT) { layoutParams.rightMargin = mRowHeaderWidth; } else { layoutParams.leftMargin = mRowHeaderWidth; } // If the corner is on the bottom the margin needs to be on the bottom if (mCornerViewLocation == CornerViewLocation.BOTTOM_LEFT || mCornerViewLocation == CornerViewLocation.BOTTOM_RIGHT) { layoutParams.bottomMargin = mColumnHeaderHeight; } else { layoutParams.topMargin = mColumnHeaderHeight; } recyclerView.setLayoutParams(layoutParams); if (isShowVerticalSeparators()) { // Add vertical item decoration to display row line on center recycler view recyclerView.addItemDecoration(getVerticalItemDecoration()); } return recyclerView; } public void setAdapter(@Nullable AbstractTableAdapter tableAdapter) { if (tableAdapter != null) { this.mTableAdapter = tableAdapter; this.mTableAdapter.setRowHeaderWidth(mRowHeaderWidth); this.mTableAdapter.setColumnHeaderHeight(mColumnHeaderHeight); this.mTableAdapter.setTableView(this); // set adapters mColumnHeaderRecyclerView.setAdapter(mTableAdapter.getColumnHeaderRecyclerViewAdapter()); mRowHeaderRecyclerView.setAdapter(mTableAdapter.getRowHeaderRecyclerViewAdapter()); mCellRecyclerView.setAdapter(mTableAdapter.getCellRecyclerViewAdapter()); // Create Sort Handler mColumnSortHandler = new ColumnSortHandler(this); // Create Filter Handler mFilterHandler = new FilterHandler<>(this); } } @Override public boolean hasFixedWidth() { return mHasFixedWidth; } public void setHasFixedWidth(boolean hasFixedWidth) { this.mHasFixedWidth = hasFixedWidth; // RecyclerView has also the same control to provide better performance. mColumnHeaderRecyclerView.setHasFixedSize(hasFixedWidth); } @Override public boolean isIgnoreSelectionColors() { return mIgnoreSelectionColors; } public void setIgnoreSelectionColors(boolean ignoreSelectionColor) { this.mIgnoreSelectionColors = ignoreSelectionColor; } @Override public boolean isShowHorizontalSeparators() { return mShowHorizontalSeparators; } @Override public boolean isAllowClickInsideCell(){ return mAllowClickInsideCell; } @Override public boolean isSortable() { return mIsSortable; } public void setShowHorizontalSeparators(boolean showSeparators) { this.mShowHorizontalSeparators = showSeparators; } @Override public boolean isShowVerticalSeparators() { return mShowVerticalSeparators; } public void setShowVerticalSeparators(boolean showSeparators) { this.mShowVerticalSeparators = showSeparators; } @NonNull @Override public CellRecyclerView getCellRecyclerView() { return mCellRecyclerView; } @NonNull @Override public CellRecyclerView getColumnHeaderRecyclerView() { return mColumnHeaderRecyclerView; } @NonNull @Override public CellRecyclerView getRowHeaderRecyclerView() { return mRowHeaderRecyclerView; } @NonNull @Override public ColumnHeaderLayoutManager getColumnHeaderLayoutManager() { if (mColumnHeaderLayoutManager == null) { mColumnHeaderLayoutManager = new ColumnHeaderLayoutManager(getContext(), this); if (mReverseLayout) mColumnHeaderLayoutManager.setReverseLayout(true); } return mColumnHeaderLayoutManager; } @NonNull @Override public CellLayoutManager getCellLayoutManager() { if (mCellLayoutManager == null) { mCellLayoutManager = new CellLayoutManager(getContext(), this); } return mCellLayoutManager; } @NonNull @Override public LinearLayoutManager getRowHeaderLayoutManager() { if (mRowHeaderLayoutManager == null) { mRowHeaderLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager .VERTICAL, false); } return mRowHeaderLayoutManager; } @NonNull @Override public HorizontalRecyclerViewListener getHorizontalRecyclerViewListener() { return mHorizontalRecyclerViewListener; } @NonNull @Override public VerticalRecyclerViewListener getVerticalRecyclerViewListener() { return mVerticalRecyclerListener; } @Nullable @Override public ITableViewListener getTableViewListener() { return mTableViewListener; } public void setTableViewListener(@Nullable ITableViewListener tableViewListener) { this.mTableViewListener = tableViewListener; } @Override public void sortColumn(int columnPosition, @NonNull SortState sortState) { mIsSortable = true; mColumnSortHandler.sort(columnPosition, sortState); } @Override public void sortRowHeader(@NonNull SortState sortState) { mIsSortable = true; mColumnSortHandler.sortByRowHeader(sortState); } @Override public void remeasureColumnWidth(int column) { // Remove calculated width value to be ready for recalculation. getColumnHeaderLayoutManager().removeCachedWidth(column); // Recalculate of the width values of the columns getCellLayoutManager().fitWidthSize(column, false); } @Nullable @Override public AbstractTableAdapter getAdapter() { return mTableAdapter; } @Override public void filter(@NonNull Filter filter) { mFilterHandler.filter(filter); } @Nullable @Override public FilterHandler getFilterHandler() { return mFilterHandler; } @NonNull @Override public SortState getSortingStatus(int column) { return mColumnSortHandler.getSortingStatus(column); } @Nullable @Override public SortState getRowHeaderSortingStatus() { return mColumnSortHandler.getRowHeaderSortingStatus(); } @Override public void scrollToColumnPosition(int column) { mScrollHandler.scrollToColumnPosition(column); } @Override public void scrollToColumnPosition(int column, int offset) { mScrollHandler.scrollToColumnPosition(column, offset); } @Override public void scrollToRowPosition(int row) { mScrollHandler.scrollToRowPosition(row); } @Override public void scrollToRowPosition(int row, int offset) { mScrollHandler.scrollToRowPosition(row, offset); } @NonNull public ScrollHandler getScrollHandler() { return mScrollHandler; } @Override public void showRow(int row) { mVisibilityHandler.showRow(row); } @Override public void hideRow(int row) { mVisibilityHandler.hideRow(row); } @Override public void showAllHiddenRows() { mVisibilityHandler.showAllHiddenRows(); } @Override public void clearHiddenRowList() { mVisibilityHandler.clearHideRowList(); } @Override public void showColumn(int column) { mVisibilityHandler.showColumn(column); } @Override public void hideColumn(int column) { mVisibilityHandler.hideColumn(column); } @Override public boolean isColumnVisible(int column) { return mVisibilityHandler.isColumnVisible(column); } @Override public void showAllHiddenColumns() { mVisibilityHandler.showAllHiddenColumns(); } @Override public void clearHiddenColumnList() { mVisibilityHandler.clearHideColumnList(); } @Override public boolean isRowVisible(int row) { return mVisibilityHandler.isRowVisible(row); } /** * Returns the index of the selected row, -1 if no row is selected. */ public int getSelectedRow() { return mSelectionHandler.getSelectedRowPosition(); } public void setSelectedRow(int row) { // Find the row header view holder which is located on row position. AbstractViewHolder rowViewHolder = (AbstractViewHolder) getRowHeaderRecyclerView() .findViewHolderForAdapterPosition(row); mSelectionHandler.setSelectedRowPosition(rowViewHolder, row); } /** * Returns the index of the selected column, -1 if no column is selected. */ public int getSelectedColumn() { return mSelectionHandler.getSelectedColumnPosition(); } public void setSelectedColumn(int column) { // Find the column view holder which is located on column position . AbstractViewHolder columnViewHolder = (AbstractViewHolder) getColumnHeaderRecyclerView() .findViewHolderForAdapterPosition(column); mSelectionHandler.setSelectedColumnPosition(columnViewHolder, column); } public void setSelectedCell(int column, int row) { // Find the cell view holder which is located on x,y (column,row) position. AbstractViewHolder cellViewHolder = getCellLayoutManager().getCellViewHolder(column, row); mSelectionHandler.setSelectedCellPositions(cellViewHolder, column, row); } @NonNull @Override public SelectionHandler getSelectionHandler() { return mSelectionHandler; } @Nullable @Override public ColumnSortHandler getColumnSortHandler() { return mColumnSortHandler; } @NonNull @Override public VisibilityHandler getVisibilityHandler() { return mVisibilityHandler; } @NonNull @Override public DividerItemDecoration getHorizontalItemDecoration() { if (mHorizontalItemDecoration == null) { mHorizontalItemDecoration = createItemDecoration(DividerItemDecoration.HORIZONTAL); } return mHorizontalItemDecoration; } @NonNull @Override public DividerItemDecoration getVerticalItemDecoration() { if (mVerticalItemDecoration == null) { mVerticalItemDecoration = createItemDecoration(DividerItemDecoration.VERTICAL); } return mVerticalItemDecoration; } @NonNull protected DividerItemDecoration createItemDecoration(int orientation) { DividerItemDecoration itemDecoration = new DividerItemDecoration(getContext(), orientation); Drawable divider = ContextCompat.getDrawable(getContext(), R.drawable.cell_line_divider); if (divider == null) { return itemDecoration; } // That means; There is a custom separator color from user. if (mSeparatorColor != -1) { // Change its color divider.setColorFilter(mSeparatorColor, PorterDuff.Mode.SRC_ATOP); } itemDecoration.setDrawable(divider); return itemDecoration; } /** * This method helps to change default selected color programmatically. * * @param selectedColor It must be Color int. */ public void setSelectedColor(@ColorInt int selectedColor) { this.mSelectedColor = selectedColor; } @ColorInt @Override public int getSelectedColor() { return mSelectedColor; } public void setSeparatorColor(@ColorInt int mSeparatorColor) { this.mSeparatorColor = mSeparatorColor; } @ColorInt @Override public int getSeparatorColor() { return mSeparatorColor; } /** * This method helps to change default unselected color programmatically. * * @param unSelectedColor It must be Color int. */ public void setUnSelectedColor(@ColorInt int unSelectedColor) { this.mUnSelectedColor = unSelectedColor; } @ColorInt @Override public int getUnSelectedColor() { return mUnSelectedColor; } public void setShadowColor(@ColorInt int shadowColor) { this.mShadowColor = shadowColor; } @Override public @ColorInt int getShadowColor() { return mShadowColor; } /** * get row header width * * @return size in pixel */ @Override public int getRowHeaderWidth() { return mRowHeaderWidth; } /** * set RowHeaderWidth * * @param rowHeaderWidth in pixel */ @Override public void setRowHeaderWidth(int rowHeaderWidth) { this.mRowHeaderWidth = rowHeaderWidth; // Update RowHeader layout width ViewGroup.LayoutParams layoutParamsRow = mRowHeaderRecyclerView.getLayoutParams(); layoutParamsRow.width = rowHeaderWidth; mRowHeaderRecyclerView.setLayoutParams(layoutParamsRow); mRowHeaderRecyclerView.requestLayout(); // Update ColumnHeader left margin LayoutParams layoutParamsColumn = (LayoutParams) mColumnHeaderRecyclerView.getLayoutParams(); // If the corner is on the right the margin needs to be on the right if (mCornerViewLocation == CornerViewLocation.TOP_RIGHT || mCornerViewLocation == CornerViewLocation.BOTTOM_RIGHT) { layoutParamsColumn.rightMargin = rowHeaderWidth; } else { layoutParamsColumn.leftMargin = rowHeaderWidth; } mColumnHeaderRecyclerView.setLayoutParams(layoutParamsColumn); mColumnHeaderRecyclerView.requestLayout(); // Update Cells left margin LayoutParams layoutParamsCell = (LayoutParams) mCellRecyclerView.getLayoutParams(); if (mCornerViewLocation == CornerViewLocation.TOP_RIGHT || mCornerViewLocation == CornerViewLocation.BOTTOM_RIGHT) { layoutParamsCell.rightMargin = rowHeaderWidth; } else { layoutParamsCell.leftMargin = rowHeaderWidth; } mCellRecyclerView.setLayoutParams(layoutParamsCell); mCellRecyclerView.requestLayout(); if (getAdapter() != null) { // update CornerView size getAdapter().setRowHeaderWidth(rowHeaderWidth); } } public void setColumnWidth(int columnPosition, int width) { mColumnWidthHandler.setColumnWidth(columnPosition, width); } public void setShowCornerView(boolean showCornerView){ mShowCornerView = showCornerView; } public boolean getShowCornerView(){ return mShowCornerView; } public CornerViewLocation getCornerViewLocation() { return mCornerViewLocation; } @Override public void setCornerViewLocation(CornerViewLocation cornerViewLocation) { mCornerViewLocation = cornerViewLocation; } public int getGravity() { int gravity; switch (mCornerViewLocation) { case TOP_LEFT: gravity = Gravity.TOP|Gravity.LEFT; break; case TOP_RIGHT: gravity = Gravity.TOP|Gravity.RIGHT; break; case BOTTOM_LEFT: gravity = Gravity.BOTTOM|Gravity.LEFT; break; case BOTTOM_RIGHT: gravity = Gravity.BOTTOM|Gravity.RIGHT; break; default: gravity = Gravity.TOP|Gravity.LEFT; break; } return gravity; } public boolean getReverseLayout(){ return mReverseLayout;} public void setReverseLayout(boolean reverseLayout) { mReverseLayout = reverseLayout; } @Nullable @Override protected Parcelable onSaveInstanceState() { SavedState state = new SavedState(super.onSaveInstanceState()); // Save all preferences of The TableView before the configuration changed. state.preferences = mPreferencesHandler.savePreferences(); return state; } @Override protected void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedState)) { super.onRestoreInstanceState(state); return; } SavedState savedState = (SavedState) state; super.onRestoreInstanceState(savedState.getSuperState()); // Reload the preferences mPreferencesHandler.loadPreferences(savedState.preferences); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/adapter/AbstractTableAdapter.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.adapter; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerViewAdapter; import com.evrencoskun.tableview.adapter.recyclerview.ColumnHeaderRecyclerViewAdapter; import com.evrencoskun.tableview.adapter.recyclerview.RowHeaderRecyclerViewAdapter; import java.util.ArrayList; import java.util.List; /** * Created by evrencoskun on 10/06/2017. */ public abstract class AbstractTableAdapter implements ITableAdapter { private int mRowHeaderWidth; private int mColumnHeaderHeight; private ColumnHeaderRecyclerViewAdapter mColumnHeaderRecyclerViewAdapter; private RowHeaderRecyclerViewAdapter mRowHeaderRecyclerViewAdapter; private CellRecyclerViewAdapter mCellRecyclerViewAdapter; private View mCornerView; protected List mColumnHeaderItems; protected List mRowHeaderItems; protected List> mCellItems; private ITableView mTableView; private List> dataSetChangedListeners; public void setTableView(@NonNull ITableView tableView) { mTableView = tableView; initialize(); } private void initialize() { Context context = mTableView.getContext(); // Create Column header RecyclerView Adapter mColumnHeaderRecyclerViewAdapter = new ColumnHeaderRecyclerViewAdapter<>(context, mColumnHeaderItems, this); // Create Row Header RecyclerView Adapter mRowHeaderRecyclerViewAdapter = new RowHeaderRecyclerViewAdapter<>(context, mRowHeaderItems, this); // Create Cell RecyclerView Adapter mCellRecyclerViewAdapter = new CellRecyclerViewAdapter<>(context, mCellItems, mTableView); } public void setColumnHeaderItems(@Nullable List columnHeaderItems) { if (columnHeaderItems == null) { return; } mColumnHeaderItems = columnHeaderItems; // Invalidate the cached widths for letting the view measure the cells width // from scratch. mTableView.getColumnHeaderLayoutManager().clearCachedWidths(); // Set the items to the adapter mColumnHeaderRecyclerViewAdapter.setItems(mColumnHeaderItems); dispatchColumnHeaderDataSetChangesToListeners(columnHeaderItems); } public void setRowHeaderItems(@Nullable List rowHeaderItems) { if (rowHeaderItems == null) { return; } mRowHeaderItems = rowHeaderItems; // Set the items to the adapter mRowHeaderRecyclerViewAdapter.setItems(mRowHeaderItems); dispatchRowHeaderDataSetChangesToListeners(mRowHeaderItems); } public void setCellItems(@Nullable List> cellItems) { if (cellItems == null) { return; } mCellItems = cellItems; // Invalidate the cached widths for letting the view measure the cells width // from scratch. mTableView.getCellLayoutManager().clearCachedWidths(); // Set the items to the adapter mCellRecyclerViewAdapter.setItems(mCellItems); dispatchCellDataSetChangesToListeners(mCellItems); } public void setAllItems( @Nullable List columnHeaderItems, @Nullable List rowHeaderItems, @Nullable List> cellItems ) { // Set all items setColumnHeaderItems(columnHeaderItems); setRowHeaderItems(rowHeaderItems); setCellItems(cellItems); // Control corner view updateCornerViewState(columnHeaderItems, rowHeaderItems); } private void updateCornerViewState( @Nullable List columnHeaderItems, @Nullable List rowHeaderItems ) { boolean hasColumnHeaders = columnHeaderItems != null && !columnHeaderItems.isEmpty(); boolean hasRowHeaders = rowHeaderItems != null && !rowHeaderItems.isEmpty(); boolean showCornerView = mTableView != null && mTableView.getShowCornerView(); boolean needCornerSpace = hasColumnHeaders && (hasRowHeaders || showCornerView); // Create the corner view if we need it if (mCornerView == null && needCornerSpace) { // No TableView is associated with this Adapter, so we can't create the corner view if (mTableView == null) { return; } FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( mRowHeaderWidth, mColumnHeaderHeight, mTableView.getGravity() ); // Create corner view mCornerView = onCreateCornerView((ViewGroup) mTableView); // Set the corner location mTableView.addView(mCornerView, layoutParams); } // We don't have any corner view to update if (mCornerView == null) { return; } if (needCornerSpace) { mCornerView.setVisibility(View.VISIBLE); } else { mCornerView.setVisibility(View.GONE); } } @Override public int getColumnHeaderItemViewType(int position) { return 0; } @Override public int getRowHeaderItemViewType(int position) { return 0; } @Override public int getCellItemViewType(int position) { return 0; } @Nullable @Override public View getCornerView() { return mCornerView; } public ColumnHeaderRecyclerViewAdapter getColumnHeaderRecyclerViewAdapter() { return mColumnHeaderRecyclerViewAdapter; } public RowHeaderRecyclerViewAdapter getRowHeaderRecyclerViewAdapter() { return mRowHeaderRecyclerViewAdapter; } public CellRecyclerViewAdapter getCellRecyclerViewAdapter() { return mCellRecyclerViewAdapter; } public void setRowHeaderWidth(int rowHeaderWidth) { this.mRowHeaderWidth = rowHeaderWidth; if (mCornerView != null) { ViewGroup.LayoutParams layoutParams = mCornerView.getLayoutParams(); layoutParams.width = rowHeaderWidth; } } public void setColumnHeaderHeight(int columnHeaderHeight) { this.mColumnHeaderHeight = columnHeaderHeight; } @Nullable public CH getColumnHeaderItem(int position) { if ((mColumnHeaderItems == null || mColumnHeaderItems.isEmpty()) || position < 0 || position >= mColumnHeaderItems.size()) { return null; } return mColumnHeaderItems.get(position); } @Nullable public RH getRowHeaderItem(int position) { if ((mRowHeaderItems == null || mRowHeaderItems.isEmpty()) || position < 0 || position >= mRowHeaderItems.size()) { return null; } return mRowHeaderItems.get(position); } @Nullable public C getCellItem(int columnPosition, int rowPosition) { if ((mCellItems == null || mCellItems.isEmpty()) || columnPosition < 0 || rowPosition >= mCellItems.size() || mCellItems.get(rowPosition) == null || rowPosition < 0 || columnPosition >= mCellItems.get(rowPosition).size()) { return null; } return mCellItems.get(rowPosition).get(columnPosition); } @Nullable public List getCellRowItems(int rowPosition) { return (List) mCellRecyclerViewAdapter.getItem(rowPosition); } public void removeRow(int rowPosition) { mCellRecyclerViewAdapter.deleteItem(rowPosition); mRowHeaderRecyclerViewAdapter.deleteItem(rowPosition); } public void removeRow(int rowPosition, boolean updateRowHeader) { mCellRecyclerViewAdapter.deleteItem(rowPosition); // To be able update the row header data if (updateRowHeader) { rowPosition = mRowHeaderRecyclerViewAdapter.getItemCount() - 1; // Cell RecyclerView items should be notified. // Because, other items stores the old row position. mCellRecyclerViewAdapter.notifyDataSetChanged(); } mRowHeaderRecyclerViewAdapter.deleteItem(rowPosition); } public void removeRowRange(int rowPositionStart, int itemCount) { mCellRecyclerViewAdapter.deleteItemRange(rowPositionStart, itemCount); mRowHeaderRecyclerViewAdapter.deleteItemRange(rowPositionStart, itemCount); } public void removeRowRange(int rowPositionStart, int itemCount, boolean updateRowHeader) { mCellRecyclerViewAdapter.deleteItemRange(rowPositionStart, itemCount); // To be able update the row header data sets if (updateRowHeader) { rowPositionStart = mRowHeaderRecyclerViewAdapter.getItemCount() - 1 - itemCount; // Cell RecyclerView items should be notified. // Because, other items stores the old row position. mCellRecyclerViewAdapter.notifyDataSetChanged(); } mRowHeaderRecyclerViewAdapter.deleteItemRange(rowPositionStart, itemCount); } public void addRow(int rowPosition, @Nullable RH rowHeaderItem, @Nullable List cellItems) { mCellRecyclerViewAdapter.addItem(rowPosition, cellItems); mRowHeaderRecyclerViewAdapter.addItem(rowPosition, rowHeaderItem); } public void addRowRange(int rowPositionStart, @Nullable List rowHeaderItem, @Nullable List> cellItems) { mRowHeaderRecyclerViewAdapter.addItemRange(rowPositionStart, rowHeaderItem); mCellRecyclerViewAdapter.addItemRange(rowPositionStart, cellItems); } public void changeRowHeaderItem(int rowPosition, @Nullable RH rowHeaderModel) { mRowHeaderRecyclerViewAdapter.changeItem(rowPosition, rowHeaderModel); } public void changeRowHeaderItemRange(int rowPositionStart, @Nullable List rowHeaderModelList) { mRowHeaderRecyclerViewAdapter.changeItemRange(rowPositionStart, rowHeaderModelList); } public void changeCellItem(int columnPosition, int rowPosition, C cellModel) { List cellItems = (List) mCellRecyclerViewAdapter.getItem(rowPosition); if (cellItems != null && cellItems.size() > columnPosition) { // Update cell row items. cellItems.set(columnPosition, cellModel); mCellRecyclerViewAdapter.changeItem(rowPosition, cellItems); } } public void changeColumnHeader(int columnPosition, @Nullable CH columnHeaderModel) { mColumnHeaderRecyclerViewAdapter.changeItem(columnPosition, columnHeaderModel); } public void changeColumnHeaderRange(int columnPositionStart, @Nullable List columnHeaderModelList) { mColumnHeaderRecyclerViewAdapter.changeItemRange(columnPositionStart, columnHeaderModelList); } @NonNull public List getCellColumnItems(int columnPosition) { return mCellRecyclerViewAdapter.getColumnItems(columnPosition); } public void removeColumn(int columnPosition) { mColumnHeaderRecyclerViewAdapter.deleteItem(columnPosition); mCellRecyclerViewAdapter.removeColumnItems(columnPosition); } public void addColumn(int columnPosition, @Nullable CH columnHeaderItem, @NonNull List cellItems) { mColumnHeaderRecyclerViewAdapter.addItem(columnPosition, columnHeaderItem); mCellRecyclerViewAdapter.addColumnItems(columnPosition, cellItems); } public final void notifyDataSetChanged() { mColumnHeaderRecyclerViewAdapter.notifyDataSetChanged(); mRowHeaderRecyclerViewAdapter.notifyDataSetChanged(); mCellRecyclerViewAdapter.notifyCellDataSetChanged(); } @Override public ITableView getTableView() { return mTableView; } private void dispatchColumnHeaderDataSetChangesToListeners(@NonNull List newColumnHeaderItems) { if (dataSetChangedListeners != null) { for (AdapterDataSetChangedListener listener : dataSetChangedListeners) { listener.onColumnHeaderItemsChanged(newColumnHeaderItems); } } } private void dispatchRowHeaderDataSetChangesToListeners(@NonNull final List newRowHeaderItems) { if (dataSetChangedListeners != null) { for (AdapterDataSetChangedListener listener : dataSetChangedListeners) { listener.onRowHeaderItemsChanged(newRowHeaderItems); } } } private void dispatchCellDataSetChangesToListeners(@NonNull List> newCellItems) { if (dataSetChangedListeners != null) { for (AdapterDataSetChangedListener listener : dataSetChangedListeners) { listener.onCellItemsChanged(newCellItems); } } } /** * Sets the listener for changes of data set on the TableView. * * @param listener The AdapterDataSetChangedListener listener. */ @Override public void addAdapterDataSetChangedListener(@NonNull AdapterDataSetChangedListener listener) { if (dataSetChangedListeners == null) { dataSetChangedListeners = new ArrayList<>(); } dataSetChangedListeners.add(listener); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/adapter/AdapterDataSetChangedListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.adapter; import androidx.annotation.NonNull; import java.util.List; public abstract class AdapterDataSetChangedListener { /** * Dispatches changes on column header items to listener. * * @param columnHeaderItems The current column header items. */ public void onColumnHeaderItemsChanged(@NonNull List columnHeaderItems) { } /** * Dispatches changes on row header items to listener. * * @param rowHeaderItems The current row header items. */ public void onRowHeaderItemsChanged(@NonNull List rowHeaderItems) { } /** * Dispatches changes on cell items to listener. * * @param cellItems The current cell items. */ public void onCellItemsChanged(@NonNull List> cellItems) { } /** * Dispatches the changes on column header, row header and cell items. * * @param columnHeaderItems The current column header items. * @param rowHeaderItems The current row header items. * @param cellItems The current cell items. */ public void onDataSetChanged( List columnHeaderItems, List rowHeaderItems, List> cellItems) { } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/adapter/ITableAdapter.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.adapter; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; /** * Created by evrencoskun on 10/06/2017. */ public interface ITableAdapter { int getColumnHeaderItemViewType(int position); int getRowHeaderItemViewType(int position); int getCellItemViewType(int position); View getCornerView(); @NonNull AbstractViewHolder onCreateCellViewHolder(@NonNull ViewGroup parent, int viewType); void onBindCellViewHolder(@NonNull AbstractViewHolder holder, @Nullable C cellItemModel, int columnPosition, int rowPosition); @NonNull AbstractViewHolder onCreateColumnHeaderViewHolder(@NonNull ViewGroup parent, int viewType); void onBindColumnHeaderViewHolder(@NonNull AbstractViewHolder holder, @Nullable CH columnHeaderItemModel, int columnPosition); @NonNull AbstractViewHolder onCreateRowHeaderViewHolder(@NonNull ViewGroup parent, int viewType); void onBindRowHeaderViewHolder(@NonNull AbstractViewHolder holder, @Nullable RH rowHeaderItemModel, int rowPosition); @NonNull View onCreateCornerView(@NonNull ViewGroup parent); ITableView getTableView(); /** * Sets the listener for changes of data set on the TableView. * * @param listener The AdapterDataSetChangedListener listener. */ void addAdapterDataSetChangedListener(@NonNull AdapterDataSetChangedListener listener); } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/adapter/recyclerview/AbstractRecyclerViewAdapter.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.adapter.recyclerview; import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import java.util.ArrayList; import java.util.List; /** * Created by evrencoskun on 10/06/2017. */ public abstract class AbstractRecyclerViewAdapter extends RecyclerView .Adapter { @NonNull protected List mItemList; @NonNull protected Context mContext; public AbstractRecyclerViewAdapter(@NonNull Context context) { this(context, null); } public AbstractRecyclerViewAdapter(@NonNull Context context, @Nullable List itemList) { mContext = context; if (itemList == null) { mItemList = new ArrayList<>(); } else { setItems(itemList); } } @Override public int getItemCount() { return mItemList.size(); } @NonNull public List getItems() { return mItemList; } public void setItems(@NonNull List itemList) { mItemList = new ArrayList<>(itemList); this.notifyDataSetChanged(); } public void setItems(@NonNull List itemList, boolean notifyDataSet) { mItemList = new ArrayList<>(itemList); if (notifyDataSet) { this.notifyDataSetChanged(); } } @Nullable public T getItem(int position) { if (mItemList.isEmpty() || position < 0 || position >= mItemList.size()) { return null; } return mItemList.get(position); } public void deleteItem(int position) { if (position != RecyclerView.NO_POSITION) { mItemList.remove(position); notifyItemRemoved(position); } } public void deleteItemRange(int positionStart, int itemCount) { for (int i = positionStart + itemCount - 1; i >= positionStart; i--) { if (i != RecyclerView.NO_POSITION) { mItemList.remove(i); } } notifyItemRangeRemoved(positionStart, itemCount); } public void addItem(int position, @Nullable T item) { if (position != RecyclerView.NO_POSITION && item != null) { mItemList.add(position, item); notifyItemInserted(position); } } public void addItemRange(int positionStart, @Nullable List items) { if (items != null) { for (int i = 0; i < items.size(); i++) { mItemList.add((i + positionStart), items.get(i)); } notifyItemRangeInserted(positionStart, items.size()); } } public void changeItem(int position, @Nullable T item) { if (position != RecyclerView.NO_POSITION && item != null) { mItemList.set(position, item); notifyItemChanged(position); } } public void changeItemRange(int positionStart, @Nullable List items) { if (items != null && mItemList.size() > positionStart + items.size()) { for (int i = 0; i < items.size(); i++) { mItemList.set(i + positionStart, items.get(i)); } notifyItemRangeChanged(positionStart, items.size()); } } @Override public int getItemViewType(int position) { return 1; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/adapter/recyclerview/CellRecyclerView.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.adapter.recyclerview; import android.content.Context; import android.util.Log; import android.view.View; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.R; import com.evrencoskun.tableview.listener.scroll.HorizontalRecyclerViewListener; import com.evrencoskun.tableview.listener.scroll.VerticalRecyclerViewListener; /** * Created by evrencoskun on 19/06/2017. */ public class CellRecyclerView extends RecyclerView { private static final String LOG_TAG = CellRecyclerView.class.getSimpleName(); private int mScrolledX = 0; private int mScrolledY = 0; private boolean mIsHorizontalScrollListenerRemoved = true; private boolean mIsVerticalScrollListenerRemoved = true; public CellRecyclerView(@NonNull Context context) { super(context); // These are necessary. this.setHasFixedSize(false); this.setNestedScrollingEnabled(false); // These are for better scrolling process. this.setItemViewCacheSize(context.getResources().getInteger(R.integer .default_item_cache_size)); this.setDrawingCacheEnabled(true); this.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH); } @Override public void onScrolled(int dx, int dy) { mScrolledX += dx; mScrolledY += dy; super.onScrolled(dx, dy); } public int getScrolledX() { return mScrolledX; } public void clearScrolledX() { mScrolledX = 0; } public int getScrolledY() { return mScrolledY; } @Override public void addOnScrollListener(@NonNull OnScrollListener listener) { if (listener instanceof HorizontalRecyclerViewListener) { if (mIsHorizontalScrollListenerRemoved) { mIsHorizontalScrollListenerRemoved = false; super.addOnScrollListener(listener); } else { // Do not let add the listener Log.w(LOG_TAG, "mIsHorizontalScrollListenerRemoved has been tried to add itself " + "before remove the old one"); } } else if (listener instanceof VerticalRecyclerViewListener) { if (mIsVerticalScrollListenerRemoved) { mIsVerticalScrollListenerRemoved = false; super.addOnScrollListener(listener); } else { // Do not let add the listener Log.w(LOG_TAG, "mIsVerticalScrollListenerRemoved has been tried to add itself " + "before remove the old one"); } } else { super.addOnScrollListener(listener); } } @Override public void removeOnScrollListener(@NonNull OnScrollListener listener) { if (listener instanceof HorizontalRecyclerViewListener) { if (mIsHorizontalScrollListenerRemoved) { // Do not let remove the listener Log.e(LOG_TAG, "HorizontalRecyclerViewListener has been tried to remove " + "itself before add new one"); } else { mIsHorizontalScrollListenerRemoved = true; super.removeOnScrollListener(listener); } } else if (listener instanceof VerticalRecyclerViewListener) { if (mIsVerticalScrollListenerRemoved) { // Do not let remove the listener Log.e(LOG_TAG, "mIsVerticalScrollListenerRemoved has been tried to remove " + "itself before add new one"); } else { mIsVerticalScrollListenerRemoved = true; super.removeOnScrollListener(listener); } } else { super.removeOnScrollListener(listener); } } public boolean isHorizontalScrollListenerRemoved() { return mIsHorizontalScrollListenerRemoved; } public boolean isScrollOthers() { return !mIsHorizontalScrollListenerRemoved; } /** * Begin a standard fling with an initial velocity along each axis in pixels per second. * If the velocity given is below the system-defined minimum this method will return false * and no fling will occur. * * @param velocityX Initial horizontal velocity in pixels per second * @param velocityY Initial vertical velocity in pixels per second * @return true if the fling was started, false if the velocity was too low to fling or * LayoutManager does not support scrolling in the axis fling is issued. * @see LayoutManager#canScrollVertically() * @see LayoutManager#canScrollHorizontally() */ @Override public boolean fling(int velocityX, int velocityY) { // Adjust speeds to be able to provide smoother scroll. //velocityX *= 0.6; //velocityY *= 0.6; return super.fling(velocityX, velocityY); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/adapter/recyclerview/CellRecyclerViewAdapter.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.adapter.recyclerview; import android.content.Context; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder.SelectionState; import com.evrencoskun.tableview.handler.ScrollHandler; import com.evrencoskun.tableview.handler.SelectionHandler; import com.evrencoskun.tableview.layoutmanager.CellLayoutManager; import com.evrencoskun.tableview.layoutmanager.ColumnLayoutManager; import com.evrencoskun.tableview.listener.itemclick.CellRecyclerViewItemClickListener; import java.util.ArrayList; import java.util.List; /** * Created by evrencoskun on 10/06/2017. */ public class CellRecyclerViewAdapter extends AbstractRecyclerViewAdapter { @NonNull private final ITableView mTableView; @NonNull private final RecyclerView.RecycledViewPool mRecycledViewPool; // This is for testing purpose private int mRecyclerViewId = 0; public CellRecyclerViewAdapter(@NonNull Context context, @Nullable List itemList, @NonNull ITableView tableView) { super(context, itemList); this.mTableView = tableView; // Create view pool to share Views between multiple RecyclerViews. mRecycledViewPool = new RecyclerView.RecycledViewPool(); //TODO set the right value. //mRecycledViewPool.setMaxRecycledViews(0, 110); } @NonNull @Override public AbstractViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { // Create a RecyclerView as a Row of the CellRecyclerView CellRecyclerView recyclerView = new CellRecyclerView(mContext); // Use the same view pool recyclerView.setRecycledViewPool(mRecycledViewPool); if (mTableView.isShowHorizontalSeparators()) { // Add divider recyclerView.addItemDecoration(mTableView.getHorizontalItemDecoration()); } // To get better performance for fixed size TableView recyclerView.setHasFixedSize(mTableView.hasFixedWidth()); // set touch mHorizontalListener to scroll synchronously recyclerView.addOnItemTouchListener(mTableView.getHorizontalRecyclerViewListener()); // Add Item click listener for cell views if (mTableView.isAllowClickInsideCell()) { recyclerView.addOnItemTouchListener(new CellRecyclerViewItemClickListener(recyclerView, mTableView)); } // Set the Column layout manager that helps the fit width of the cell and column header // and it also helps to locate the scroll position of the horizontal recyclerView // which is row recyclerView ColumnLayoutManager mColumnLayoutManager = new ColumnLayoutManager(mContext, mTableView); if (mTableView.getReverseLayout()) mColumnLayoutManager.setReverseLayout(true); recyclerView.setLayoutManager(mColumnLayoutManager); // Create CellRow adapter recyclerView.setAdapter(new CellRowRecyclerViewAdapter<>(mContext, mTableView)); // This is for testing purpose to find out which recyclerView is displayed. recyclerView.setId(mRecyclerViewId); mRecyclerViewId++; return new CellRowViewHolder(recyclerView); } @Override public void onBindViewHolder(@NonNull AbstractViewHolder holder, int yPosition) { CellRowViewHolder viewHolder = (CellRowViewHolder) holder; CellRowRecyclerViewAdapter viewAdapter = (CellRowRecyclerViewAdapter) viewHolder .recyclerView.getAdapter(); // Get the list List rowList = (List) mItemList.get(yPosition); // Set Row position viewAdapter.setYPosition(yPosition); // Set the list to the adapter viewAdapter.setItems(rowList); } @Override public void onViewAttachedToWindow(@NonNull AbstractViewHolder holder) { super.onViewAttachedToWindow(holder); CellRowViewHolder viewHolder = (CellRowViewHolder) holder; ScrollHandler scrollHandler = mTableView.getScrollHandler(); // The below code helps to display a new attached recyclerView on exact scrolled position. ((ColumnLayoutManager) viewHolder.recyclerView.getLayoutManager()) .scrollToPositionWithOffset(scrollHandler.getColumnPosition(), scrollHandler .getColumnPositionOffset()); SelectionHandler selectionHandler = mTableView.getSelectionHandler(); if (selectionHandler.isAnyColumnSelected()) { AbstractViewHolder cellViewHolder = (AbstractViewHolder) viewHolder.recyclerView .findViewHolderForAdapterPosition(selectionHandler.getSelectedColumnPosition()); if (cellViewHolder != null) { // Control to ignore selection color if (!mTableView.isIgnoreSelectionColors()) { cellViewHolder.setBackgroundColor(mTableView.getSelectedColor()); } cellViewHolder.setSelected(SelectionState.SELECTED); } } else if (selectionHandler.isRowSelected(holder.getBindingAdapterPosition())) { selectionHandler.changeSelectionOfRecyclerView(viewHolder.recyclerView, SelectionState.SELECTED, mTableView.getSelectedColor()); } } @Override public void onViewDetachedFromWindow(@NonNull AbstractViewHolder holder) { super.onViewDetachedFromWindow(holder); // Clear selection status of the view holder mTableView.getSelectionHandler().changeSelectionOfRecyclerView(((CellRowViewHolder) holder).recyclerView, SelectionState.UNSELECTED, mTableView.getUnSelectedColor()); } @Override public void onViewRecycled(@NonNull AbstractViewHolder holder) { super.onViewRecycled(holder); CellRowViewHolder viewHolder = (CellRowViewHolder) holder; // ScrolledX should be cleared at that time. Because we need to prepare each // recyclerView // at onViewAttachedToWindow process. viewHolder.recyclerView.clearScrolledX(); } static class CellRowViewHolder extends AbstractViewHolder { final CellRecyclerView recyclerView; CellRowViewHolder(@NonNull View itemView) { super(itemView); recyclerView = (CellRecyclerView) itemView; } } public void notifyCellDataSetChanged() { CellRecyclerView[] visibleRecyclerViews = mTableView.getCellLayoutManager() .getVisibleCellRowRecyclerViews(); if (visibleRecyclerViews.length > 0) { for (CellRecyclerView cellRowRecyclerView : visibleRecyclerViews) { if (cellRowRecyclerView != null) { RecyclerView.Adapter adapter = cellRowRecyclerView.getAdapter(); if (adapter != null) { adapter.notifyDataSetChanged(); } } } } else { notifyDataSetChanged(); } } /** * This method helps to get cell item model that is located on given column position. * * @param columnPosition */ @NonNull public List getColumnItems(int columnPosition) { List cellItems = new ArrayList<>(); for (int i = 0; i < mItemList.size(); i++) { List rowList = (List) mItemList.get(i); if (rowList.size() > columnPosition) { cellItems.add(rowList.get(columnPosition)); } } return cellItems; } public void removeColumnItems(int column) { // Firstly, remove columns from visible recyclerViews. // To be able provide removing animation, we need to notify just for given column position. CellRecyclerView[] visibleRecyclerViews = mTableView.getCellLayoutManager() .getVisibleCellRowRecyclerViews(); for (CellRecyclerView cellRowRecyclerView : visibleRecyclerViews) { if (cellRowRecyclerView != null) { AbstractRecyclerViewAdapter adapter = (AbstractRecyclerViewAdapter) cellRowRecyclerView.getAdapter(); if (adapter != null) { adapter.deleteItem(column); } } } // Lets change the model list silently // Create a new list which the column is already removed. List> cellItems = new ArrayList<>(); for (int i = 0; i < mItemList.size(); i++) { List rowList = new ArrayList<>((List) mItemList.get(i)); if (rowList.size() > column) { rowList.remove(column); } cellItems.add(rowList); } // Change data without notifying. Because we already did for visible recyclerViews. setItems((List) cellItems, false); } public void addColumnItems(int column, @NonNull List cellColumnItems) { // It should be same size with exist model list. if (cellColumnItems.size() != mItemList.size() || cellColumnItems.contains(null)) { return; } // Firstly, add columns from visible recyclerViews. // To be able provide removing animation, we need to notify just for given column position. CellLayoutManager layoutManager = mTableView.getCellLayoutManager(); for (int i = layoutManager.findFirstVisibleItemPosition(); i < layoutManager .findLastVisibleItemPosition() + 1; i++) { // Get the cell row recyclerView that is located on i position RecyclerView cellRowRecyclerView = (RecyclerView) layoutManager.findViewByPosition(i); // Add the item using its adapter. ((AbstractRecyclerViewAdapter) cellRowRecyclerView.getAdapter()).addItem(column, cellColumnItems.get(i)); } // Lets change the model list silently List> cellItems = new ArrayList<>(); for (int i = 0; i < mItemList.size(); i++) { List rowList = new ArrayList<>((List) mItemList.get(i)); if (rowList.size() > column) { rowList.add(column, cellColumnItems.get(i)); } cellItems.add(rowList); } // Change data without notifying. Because we already did for visible recyclerViews. setItems((List) cellItems, false); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/adapter/recyclerview/CellRowRecyclerViewAdapter.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.adapter.recyclerview; import android.content.Context; import android.view.ViewGroup; import androidx.annotation.NonNull; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.ITableAdapter; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder.SelectionState; /** * Created by evrencoskun on 10/06/2017. */ public class CellRowRecyclerViewAdapter extends AbstractRecyclerViewAdapter { private int mYPosition; private final ITableAdapter mTableAdapter; @NonNull private final ITableView mTableView; public CellRowRecyclerViewAdapter(@NonNull Context context, @NonNull ITableView tableView) { super(context, null); this.mTableAdapter = tableView.getAdapter(); this.mTableView = tableView; } @NonNull @Override public AbstractViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return mTableAdapter.onCreateCellViewHolder(parent, viewType); } @Override public void onBindViewHolder(@NonNull final AbstractViewHolder holder, final int xPosition) { mTableAdapter.onBindCellViewHolder(holder, getItem(xPosition), xPosition, mYPosition); } public int getYPosition() { return mYPosition; } public void setYPosition(int rowPosition) { mYPosition = rowPosition; } @Override public int getItemViewType(int position) { return mTableAdapter.getCellItemViewType(position); } @Override public void onViewAttachedToWindow(@NonNull AbstractViewHolder viewHolder) { super.onViewAttachedToWindow(viewHolder); SelectionState selectionState = mTableView.getSelectionHandler().getCellSelectionState (viewHolder.getBindingAdapterPosition(), mYPosition); // Control to ignore selection color if (!mTableView.isIgnoreSelectionColors()) { // Change the background color of the view considering selected row/cell position. if (selectionState == SelectionState.SELECTED) { viewHolder.setBackgroundColor(mTableView.getSelectedColor()); } else { viewHolder.setBackgroundColor(mTableView.getUnSelectedColor()); } } // Change selection status viewHolder.setSelected(selectionState); } @Override public boolean onFailedToRecycleView(@NonNull AbstractViewHolder holder) { return holder.onFailedToRecycleView(); } @Override public void onViewRecycled(@NonNull AbstractViewHolder holder) { super.onViewRecycled(holder); holder.onViewRecycled(); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/adapter/recyclerview/ColumnHeaderRecyclerViewAdapter.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.adapter.recyclerview; import android.content.Context; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.ITableAdapter; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractSorterViewHolder; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder.SelectionState; import com.evrencoskun.tableview.sort.ColumnSortHelper; import com.evrencoskun.tableview.sort.SortState; import java.util.List; /** * Created by evrencoskun on 10/06/2017. */ public class ColumnHeaderRecyclerViewAdapter extends AbstractRecyclerViewAdapter { @NonNull private final ITableAdapter mTableAdapter; private final ITableView mTableView; private ColumnSortHelper mColumnSortHelper; public ColumnHeaderRecyclerViewAdapter(@NonNull Context context, @Nullable List itemList, @NonNull ITableAdapter tableAdapter) { super(context, itemList); this.mTableAdapter = tableAdapter; this.mTableView = tableAdapter.getTableView(); } @NonNull @Override public AbstractViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return mTableAdapter.onCreateColumnHeaderViewHolder(parent, viewType); } @Override public void onBindViewHolder(@NonNull AbstractViewHolder holder, int position) { mTableAdapter.onBindColumnHeaderViewHolder(holder, getItem(position), position); } @Override public int getItemViewType(int position) { return mTableAdapter.getColumnHeaderItemViewType(position); } @Override public void onViewAttachedToWindow(@NonNull AbstractViewHolder viewHolder) { super.onViewAttachedToWindow(viewHolder); SelectionState selectionState = mTableView.getSelectionHandler().getColumnSelectionState (viewHolder.getBindingAdapterPosition()); // Control to ignore selection color if (!mTableView.isIgnoreSelectionColors()) { // Change background color of the view considering it's selected state mTableView.getSelectionHandler().changeColumnBackgroundColorBySelectionStatus (viewHolder, selectionState); } // Change selection status viewHolder.setSelected(selectionState); // Control whether the TableView is sortable or not. if (mTableView.isSortable()) { if (viewHolder instanceof AbstractSorterViewHolder) { // Get its sorting state SortState state = getColumnSortHelper().getSortingStatus(viewHolder .getBindingAdapterPosition()); // Fire onSortingStatusChanged ((AbstractSorterViewHolder) viewHolder).onSortingStatusChanged(state); } } } @NonNull public ColumnSortHelper getColumnSortHelper() { if (mColumnSortHelper == null) { // It helps to store sorting state of column headers this.mColumnSortHelper = new ColumnSortHelper(mTableView.getColumnHeaderLayoutManager ()); } return mColumnSortHelper; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/adapter/recyclerview/RowHeaderRecyclerViewAdapter.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.adapter.recyclerview; import android.content.Context; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.ITableAdapter; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder.SelectionState; import com.evrencoskun.tableview.sort.RowHeaderSortHelper; import java.util.List; /** * Created by evrencoskun on 10/06/2017. */ public class RowHeaderRecyclerViewAdapter extends AbstractRecyclerViewAdapter { @NonNull private final ITableAdapter mTableAdapter; private final ITableView mTableView; private RowHeaderSortHelper mRowHeaderSortHelper; public RowHeaderRecyclerViewAdapter(@NonNull Context context, @Nullable List itemList, @NonNull ITableAdapter tableAdapter) { super(context, itemList); this.mTableAdapter = tableAdapter; this.mTableView = tableAdapter.getTableView(); } @NonNull @Override public AbstractViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return mTableAdapter.onCreateRowHeaderViewHolder(parent, viewType); } @Override public void onBindViewHolder(@NonNull AbstractViewHolder holder, int position) { mTableAdapter.onBindRowHeaderViewHolder(holder, getItem(position), position); } @Override public int getItemViewType(int position) { return mTableAdapter.getRowHeaderItemViewType(position); } @Override public void onViewAttachedToWindow(@NonNull AbstractViewHolder viewHolder) { super.onViewAttachedToWindow(viewHolder); SelectionState selectionState = mTableView.getSelectionHandler().getRowSelectionState (viewHolder.getBindingAdapterPosition()); // Control to ignore selection color if (!mTableView.isIgnoreSelectionColors()) { // Change background color of the view considering it's selected state mTableView.getSelectionHandler().changeRowBackgroundColorBySelectionStatus (viewHolder, selectionState); } // Change selection status viewHolder.setSelected(selectionState); } @NonNull public RowHeaderSortHelper getRowHeaderSortHelper() { if (mRowHeaderSortHelper == null) { // It helps to store sorting state of row headers this.mRowHeaderSortHelper = new RowHeaderSortHelper(); } return mRowHeaderSortHelper; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/adapter/recyclerview/holder/AbstractSorterViewHolder.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.adapter.recyclerview.holder; import android.view.View; import androidx.annotation.NonNull; import com.evrencoskun.tableview.sort.SortState; /** * Created by evrencoskun on 16.12.2017. */ public class AbstractSorterViewHolder extends AbstractViewHolder { @NonNull private SortState mSortState = SortState.UNSORTED; public AbstractSorterViewHolder(@NonNull View itemView) { super(itemView); } public void onSortingStatusChanged(@NonNull SortState pSortState) { this.mSortState = pSortState; } @NonNull public SortState getSortState() { return mSortState; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/adapter/recyclerview/holder/AbstractViewHolder.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.adapter.recyclerview.holder; import android.view.View; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; /** * Created by evrencoskun on 23/10/2017. */ public abstract class AbstractViewHolder extends RecyclerView.ViewHolder { public enum SelectionState {SELECTED, UNSELECTED, SHADOWED} // Default value @NonNull private SelectionState m_eState = SelectionState.UNSELECTED; public AbstractViewHolder(@NonNull View itemView) { super(itemView); } public void setSelected(@NonNull SelectionState selectionState) { m_eState = selectionState; if (selectionState == SelectionState.SELECTED) { itemView.setSelected(true); } else if (selectionState == SelectionState.UNSELECTED) { itemView.setSelected(false); } } public boolean isSelected() { return m_eState == SelectionState.SELECTED; } public boolean isShadowed() { return m_eState == SelectionState.SHADOWED; } public void setBackgroundColor(@ColorInt int p_nColor) { itemView.setBackgroundColor(p_nColor); } public void onViewRecycled() { } public boolean onFailedToRecycleView() { return false; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/filter/Filter.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.filter; import androidx.annotation.NonNull; import com.evrencoskun.tableview.ITableView; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Class used to store multiple filters for the TableView filtering feature. */ public class Filter { /** * List of filter items to be used for filtering. */ @NonNull private final List filterItems; /** * The TableView instance used in this scope. */ @NonNull private final ITableView tableView; /** * @param tableView The TableView to be filtered. */ public Filter(@NonNull ITableView tableView) { this.tableView = tableView; this.filterItems = new ArrayList<>(); } /** * Adds new filter item to the list. This should be used strictly once * for filtering the whole table. * * @param filter the filter string. */ public void set(@NonNull String filter) { set(-1, filter); } /** * Adds new filter item to the list. The filter will be used on the * specified column. * * @param column the target column for filtering. * @param filter the filter string. */ public void set(int column, @NonNull String filter) { final FilterItem filterItem = new FilterItem( column == -1 ? FilterType.ALL : FilterType.COLUMN, column, filter ); if (isAlreadyFiltering(column, filterItem)) { if (filter.isEmpty()) { remove(column, filterItem); } else { update(column, filterItem); } } else if (!filter.isEmpty()) { add(filterItem); } } /** * Adds new filter item to the list of this class. * * @param filterItem The filter item to be added to the list. */ private void add(@NonNull FilterItem filterItem) { filterItems.add(filterItem); tableView.filter(this); } /** * Removes a filter item from the list of this class. * * @param column The column to be checked for removing the filter item. * @param filterItem The filter item to be removed. */ private void remove(int column, @NonNull FilterItem filterItem) { // This would remove a FilterItem from the Filter list when the filter is cleared. // Used Iterator for removing instead of canonical loop to prevent ConcurrentModificationException. for (Iterator filterItemIterator = filterItems.iterator(); filterItemIterator.hasNext(); ) { final FilterItem item = filterItemIterator.next(); if (column == -1 && item.getFilterType().equals(filterItem.getFilterType())) { filterItemIterator.remove(); break; } else if (item.getColumn() == filterItem.getColumn()) { filterItemIterator.remove(); break; } } tableView.filter(this); } /** * Updates a filter item from the list of this class. * * @param column The column in which filter item will be updated. * @param filterItem The updated filter item. */ private void update(int column, @NonNull FilterItem filterItem) { // This would update an existing FilterItem from the Filter list. // Used Iterator for updating instead of canonical loop to prevent ConcurrentModificationException. for (Iterator filterItemIterator = filterItems.iterator(); filterItemIterator.hasNext(); ) { final FilterItem item = filterItemIterator.next(); if (column == -1 && item.getFilterType().equals(filterItem.getFilterType())) { filterItems.set(filterItems.indexOf(item), filterItem); break; } else if (item.getColumn() == filterItem.getColumn()) { filterItems.set(filterItems.indexOf(item), filterItem); break; } } tableView.filter(this); } /** * Method to check if a filter item is already added based on the column to be filtered. * * @param column The column to be checked if the list is already filtering. * @param filterItem The filter item to be checked. * @return True if a filter item for a specific column or for ALL is already in the list. */ private boolean isAlreadyFiltering(int column, @NonNull FilterItem filterItem) { // This would determine if Filter is already filtering ALL or a specified COLUMN. for (FilterItem item : filterItems) { if (column == -1 && item.getFilterType().equals(filterItem.getFilterType())) { return true; } else if (item.getColumn() == filterItem.getColumn()) { return true; } } return false; } /** * Returns the list of filter items. * * @return The list of filter items. */ @NonNull public List getFilterItems() { return this.filterItems; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/filter/FilterChangedListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.filter; import androidx.annotation.NonNull; import java.util.List; public abstract class FilterChangedListener { /** * Called when a filter has been changed. * * @param filteredCellItems The list of filtered cell items. * @param filteredRowHeaderItems The list of filtered row items. */ public void onFilterChanged(@NonNull List> filteredCellItems, @NonNull List filteredRowHeaderItems) { } /** * Called when the TableView filters are cleared. * * @param originalCellItems The unfiltered cell item list. * @param originalRowHeaderItems The unfiltered row header list. */ public void onFilterCleared(@NonNull List> originalCellItems, @NonNull List originalRowHeaderItems) { } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/filter/FilterItem.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.filter; import androidx.annotation.NonNull; public class FilterItem { @NonNull private final FilterType filterType; @NonNull private final String filter; private final int column; public FilterItem(@NonNull FilterType type, int column, @NonNull String filter) { this.filterType = type; this.column = column; this.filter = filter; } @NonNull public FilterType getFilterType() { return filterType; } @NonNull public String getFilter() { return filter; } public int getColumn() { return column; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/filter/FilterType.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.filter; /** * The type of filter based on the target. */ public enum FilterType { /** * Filters only a specific column. */ COLUMN, /** * Filters all the data in the TableView. */ ALL } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/filter/IFilterableModel.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.filter; import androidx.annotation.NonNull; public interface IFilterableModel { /** * Filterable query string for this object. * * @return query string for this object to be used in filtering. */ @NonNull String getFilterableKeyword(); } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/handler/ColumnSortHandler.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.handler; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.DiffUtil; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerViewAdapter; import com.evrencoskun.tableview.adapter.recyclerview.ColumnHeaderRecyclerViewAdapter; import com.evrencoskun.tableview.adapter.recyclerview.RowHeaderRecyclerViewAdapter; import com.evrencoskun.tableview.sort.ColumnForRowHeaderSortComparator; import com.evrencoskun.tableview.sort.ColumnSortCallback; import com.evrencoskun.tableview.sort.ColumnSortComparator; import com.evrencoskun.tableview.sort.ColumnSortStateChangedListener; import com.evrencoskun.tableview.sort.ISortableModel; import com.evrencoskun.tableview.sort.RowHeaderForCellSortComparator; import com.evrencoskun.tableview.sort.RowHeaderSortCallback; import com.evrencoskun.tableview.sort.RowHeaderSortComparator; import com.evrencoskun.tableview.sort.SortState; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Created by evrencoskun on 24.11.2017. */ public class ColumnSortHandler { private final CellRecyclerViewAdapter> mCellRecyclerViewAdapter; private final RowHeaderRecyclerViewAdapter mRowHeaderRecyclerViewAdapter; private final ColumnHeaderRecyclerViewAdapter mColumnHeaderRecyclerViewAdapter; private List columnSortStateChangedListeners = new ArrayList<>(); private boolean mEnableAnimation = true; public boolean isEnableAnimation() { return mEnableAnimation; } public void setEnableAnimation(boolean mEnableAnimation) { this.mEnableAnimation = mEnableAnimation; } public ColumnSortHandler(@NonNull ITableView tableView) { this.mCellRecyclerViewAdapter = (CellRecyclerViewAdapter>) tableView.getCellRecyclerView() .getAdapter(); this.mRowHeaderRecyclerViewAdapter = (RowHeaderRecyclerViewAdapter) tableView .getRowHeaderRecyclerView().getAdapter(); this.mColumnHeaderRecyclerViewAdapter = (ColumnHeaderRecyclerViewAdapter) tableView .getColumnHeaderRecyclerView().getAdapter(); } public void sortByRowHeader(@NonNull final SortState sortState) { List originalRowHeaderList = mRowHeaderRecyclerViewAdapter.getItems(); List sortedRowHeaderList = new ArrayList<>(originalRowHeaderList); List> originalList = mCellRecyclerViewAdapter.getItems(); List> sortedList = new ArrayList<>(originalList); if (sortState != SortState.UNSORTED) { // Do descending / ascending sort Collections.sort(sortedRowHeaderList, new RowHeaderSortComparator(sortState)); // Sorting Columns/Cells using the same logic has sorting DataSet RowHeaderForCellSortComparator rowHeaderForCellSortComparator = new RowHeaderForCellSortComparator( originalRowHeaderList, originalList, sortState); Collections.sort(sortedList, rowHeaderForCellSortComparator); } mRowHeaderRecyclerViewAdapter.getRowHeaderSortHelper().setSortingStatus(sortState); // Set sorted data list swapItems(originalRowHeaderList, sortedRowHeaderList, sortedList, sortState); } public void sort(int column, @NonNull SortState sortState) { List> originalList = mCellRecyclerViewAdapter.getItems(); List> sortedList = new ArrayList<>(originalList); List originalRowHeaderList = mRowHeaderRecyclerViewAdapter.getItems(); List sortedRowHeaderList = new ArrayList<>(originalRowHeaderList); if (sortState != SortState.UNSORTED) { // Do descending / ascending sort Collections.sort(sortedList, new ColumnSortComparator(column, sortState)); // Sorting RowHeader using the same logic has sorting DataSet ColumnForRowHeaderSortComparator columnForRowHeaderSortComparator = new ColumnForRowHeaderSortComparator( originalRowHeaderList, originalList, column, sortState); Collections.sort(sortedRowHeaderList, columnForRowHeaderSortComparator); } // Update sorting list of column headers mColumnHeaderRecyclerViewAdapter.getColumnSortHelper().setSortingStatus(column, sortState); // Set sorted data list swapItems(originalList, sortedList, column, sortedRowHeaderList, sortState); } private void swapItems(@NonNull List oldRowHeader, @NonNull List newRowHeader, @NonNull List> newColumnItems, @NonNull SortState sortState ) { // Set new items without calling notifyCellDataSetChanged method of CellRecyclerViewAdapter mRowHeaderRecyclerViewAdapter.setItems(newRowHeader, !mEnableAnimation); mCellRecyclerViewAdapter.setItems(newColumnItems, !mEnableAnimation); if (mEnableAnimation) { // Find the differences between old cell items and new items. final RowHeaderSortCallback diffCallback = new RowHeaderSortCallback(oldRowHeader, newRowHeader); final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback); diffResult.dispatchUpdatesTo(mRowHeaderRecyclerViewAdapter); diffResult.dispatchUpdatesTo(mCellRecyclerViewAdapter); } for (ColumnSortStateChangedListener listener : columnSortStateChangedListeners) { listener.onRowHeaderSortStatusChanged(sortState); } } private void swapItems(@NonNull List> oldItems, @NonNull List> newItems, int column, @NonNull List newRowHeader, @NonNull SortState sortState) { // Set new items without calling notifyCellDataSetChanged method of CellRecyclerViewAdapter mCellRecyclerViewAdapter.setItems(newItems, !mEnableAnimation); mRowHeaderRecyclerViewAdapter.setItems(newRowHeader, !mEnableAnimation); if (mEnableAnimation) { // Find the differences between old cell items and new items. final ColumnSortCallback diffCallback = new ColumnSortCallback(oldItems, newItems, column); final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback); diffResult.dispatchUpdatesTo(mCellRecyclerViewAdapter); diffResult.dispatchUpdatesTo(mRowHeaderRecyclerViewAdapter); } for (ColumnSortStateChangedListener listener : columnSortStateChangedListeners) { listener.onColumnSortStatusChanged(column, sortState); } } public void swapItems(@NonNull List> newItems, int column) { List> oldItems = mCellRecyclerViewAdapter.getItems(); // Set new items without calling notifyCellDataSetChanged method of CellRecyclerViewAdapter mCellRecyclerViewAdapter.setItems(newItems, !mEnableAnimation); if (mEnableAnimation) { // Find the differences between old cell items and new items. final ColumnSortCallback diffCallback = new ColumnSortCallback(oldItems, newItems, column); final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback); diffResult.dispatchUpdatesTo(mCellRecyclerViewAdapter); diffResult.dispatchUpdatesTo(mRowHeaderRecyclerViewAdapter); } } @NonNull public SortState getSortingStatus(int column) { return mColumnHeaderRecyclerViewAdapter.getColumnSortHelper().getSortingStatus(column); } @Nullable public SortState getRowHeaderSortingStatus() { return mRowHeaderRecyclerViewAdapter.getRowHeaderSortHelper().getSortingStatus(); } /** * Sets the listener for the changes in column sorting. * * @param listener ColumnSortStateChangedListener listener. */ public void addColumnSortStateChangedListener(@NonNull ColumnSortStateChangedListener listener) { if (columnSortStateChangedListeners == null) { columnSortStateChangedListeners = new ArrayList<>(); } columnSortStateChangedListeners.add(listener); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/handler/ColumnWidthHandler.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.handler; import androidx.annotation.NonNull; import com.evrencoskun.tableview.ITableView; /** * Created by evrencoskun on 25.04.2018. */ public class ColumnWidthHandler { @NonNull private final ITableView mTableView; public ColumnWidthHandler(@NonNull ITableView tableView) { mTableView = tableView; } public void setColumnWidth(int columnPosition, int width) { // Firstly set the column header cache map mTableView.getColumnHeaderLayoutManager().setCacheWidth(columnPosition, width); // Set each of cell items that is located on the column position mTableView.getCellLayoutManager().setCacheWidth(columnPosition, width); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/handler/FilterHandler.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.handler; import androidx.annotation.NonNull; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.AdapterDataSetChangedListener; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerViewAdapter; import com.evrencoskun.tableview.adapter.recyclerview.RowHeaderRecyclerViewAdapter; import com.evrencoskun.tableview.filter.Filter; import com.evrencoskun.tableview.filter.FilterChangedListener; import com.evrencoskun.tableview.filter.FilterItem; import com.evrencoskun.tableview.filter.FilterType; import com.evrencoskun.tableview.filter.IFilterableModel; import java.util.ArrayList; import java.util.List; public class FilterHandler { private final CellRecyclerViewAdapter> mCellRecyclerViewAdapter; private final RowHeaderRecyclerViewAdapter mRowHeaderRecyclerViewAdapter; private List> originalCellDataStore; private List originalRowDataStore; private List> filterChangedListeners; public FilterHandler(@NonNull ITableView tableView) { tableView.getAdapter().addAdapterDataSetChangedListener(adapterDataSetChangedListener); this.mCellRecyclerViewAdapter = (CellRecyclerViewAdapter>) tableView .getCellRecyclerView().getAdapter(); this.mRowHeaderRecyclerViewAdapter = (RowHeaderRecyclerViewAdapter) tableView .getRowHeaderRecyclerView().getAdapter(); } public void filter(@NonNull Filter filter) { if (originalCellDataStore == null || originalRowDataStore == null) { return; } List> originalCellData = new ArrayList<>(originalCellDataStore); List originalRowData = new ArrayList<>(originalRowDataStore); List> filteredCellList = new ArrayList<>(); List filteredRowList = new ArrayList<>(); if (filter.getFilterItems().isEmpty()) { filteredCellList = new ArrayList<>(originalCellDataStore); filteredRowList = new ArrayList<>(originalRowDataStore); dispatchFilterClearedToListeners(originalCellDataStore, originalRowDataStore); } else { for (int x = 0; x < filter.getFilterItems().size(); ) { final FilterItem filterItem = filter.getFilterItems().get(x); if (filterItem.getFilterType().equals(FilterType.ALL)) { for (List itemsList : originalCellData) { for (T item : itemsList) { if (item .getFilterableKeyword() .toLowerCase() .contains(filterItem .getFilter() .toLowerCase())) { filteredCellList.add(itemsList); filteredRowList.add(originalRowData.get(filteredCellList.indexOf(itemsList))); break; } } } } else { for (List itemsList : originalCellData) { if (itemsList .get(filterItem .getColumn()) .getFilterableKeyword() .toLowerCase() .contains(filterItem .getFilter() .toLowerCase())) { filteredCellList.add(itemsList); filteredRowList.add(originalRowData.get(filteredCellList.indexOf(itemsList))); } } } // If this is the last filter to be processed, the filtered lists will not be cleared. if (++x < filter.getFilterItems().size()) { originalCellData = new ArrayList<>(filteredCellList); originalRowData = new ArrayList<>(filteredRowList); filteredCellList.clear(); filteredRowList.clear(); } } } // Sets the filtered data to the TableView. mRowHeaderRecyclerViewAdapter.setItems(filteredRowList, true); mCellRecyclerViewAdapter.setItems(filteredCellList, true); // Tells the listeners that the TableView is filtered. dispatchFilterChangedToListeners(filteredCellList, filteredRowList); } @NonNull @SuppressWarnings("unchecked") private final AdapterDataSetChangedListener adapterDataSetChangedListener = new AdapterDataSetChangedListener() { @Override public void onRowHeaderItemsChanged(@NonNull List rowHeaderItems) { originalRowDataStore = new ArrayList<>(rowHeaderItems); } @Override public void onCellItemsChanged(@NonNull List cellItems) { originalCellDataStore = new ArrayList<>(cellItems); } }; private void dispatchFilterChangedToListeners( @NonNull List> filteredCellItems, @NonNull List filteredRowHeaderItems ) { if (filterChangedListeners != null) { for (FilterChangedListener listener : filterChangedListeners) { listener.onFilterChanged(filteredCellItems, filteredRowHeaderItems); } } } private void dispatchFilterClearedToListeners( @NonNull List> originalCellItems, @NonNull List originalRowHeaderItems ) { if (filterChangedListeners != null) { for (FilterChangedListener listener : filterChangedListeners) { listener.onFilterCleared(originalCellItems, originalRowHeaderItems); } } } public void addFilterChangedListener(@NonNull FilterChangedListener listener) { if (filterChangedListeners == null) { filterChangedListeners = new ArrayList<>(); } filterChangedListeners.add(listener); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/handler/PreferencesHandler.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.handler; import androidx.annotation.NonNull; import com.evrencoskun.tableview.TableView; import com.evrencoskun.tableview.preference.Preferences; /** * Created by evrencoskun on 3.03.2018. */ public class PreferencesHandler { @NonNull private final ScrollHandler scrollHandler; @NonNull private final SelectionHandler selectionHandler; public PreferencesHandler(@NonNull TableView tableView) { this.scrollHandler = tableView.getScrollHandler(); this.selectionHandler = tableView.getSelectionHandler(); } @NonNull public Preferences savePreferences() { Preferences preferences = new Preferences(); preferences.columnPosition = scrollHandler.getColumnPosition(); preferences.columnPositionOffset = scrollHandler.getColumnPositionOffset(); preferences.rowPosition = scrollHandler.getRowPosition(); preferences.rowPositionOffset = scrollHandler.getRowPositionOffset(); preferences.selectedColumnPosition = selectionHandler.getSelectedColumnPosition(); preferences.selectedRowPosition = selectionHandler.getSelectedRowPosition(); return preferences; } public void loadPreferences(@NonNull Preferences preferences) { scrollHandler.scrollToColumnPosition(preferences.columnPosition, preferences.columnPositionOffset); scrollHandler.scrollToRowPosition(preferences.rowPosition, preferences.rowPositionOffset); selectionHandler.setSelectedColumnPosition(preferences.selectedColumnPosition); selectionHandler.setSelectedRowPosition(preferences.selectedRowPosition); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/handler/ScrollHandler.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.handler; import android.view.View; import androidx.annotation.NonNull; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.layoutmanager.CellLayoutManager; import com.evrencoskun.tableview.layoutmanager.ColumnHeaderLayoutManager; import com.evrencoskun.tableview.layoutmanager.ColumnLayoutManager; /** * Created by evrencoskun on 13.01.2018. */ public class ScrollHandler { @NonNull private final ITableView mTableView; @NonNull private final CellLayoutManager mCellLayoutManager; @NonNull private final LinearLayoutManager mRowHeaderLayoutManager; @NonNull private final ColumnHeaderLayoutManager mColumnHeaderLayoutManager; public ScrollHandler(@NonNull ITableView tableView) { this.mTableView = tableView; this.mCellLayoutManager = tableView.getCellLayoutManager(); this.mRowHeaderLayoutManager = tableView.getRowHeaderLayoutManager(); this.mColumnHeaderLayoutManager = tableView.getColumnHeaderLayoutManager(); } public void scrollToColumnPosition(int columnPosition) { // TableView is not on screen yet. if (!((View) mTableView).isShown()) { // Change default value of the listener mTableView.getHorizontalRecyclerViewListener().setScrollPosition(columnPosition); } // Column Header should be scrolled firstly because of fitting column width process. scrollColumnHeader(columnPosition, 0); scrollCellHorizontally(columnPosition, 0); } public void scrollToColumnPosition(int columnPosition, int offset) { // TableView is not on screen yet. if (!((View) mTableView).isShown()) { // Change default value of the listener mTableView.getHorizontalRecyclerViewListener().setScrollPosition(columnPosition); mTableView.getHorizontalRecyclerViewListener().setScrollPositionOffset(offset); } // Column Header should be scrolled firstly because of fitting column width process. scrollColumnHeader(columnPosition, offset); scrollCellHorizontally(columnPosition, offset); } public void scrollToRowPosition(int rowPosition) { mRowHeaderLayoutManager.scrollToPosition(rowPosition); mCellLayoutManager.scrollToPosition(rowPosition); } public void scrollToRowPosition(int rowPosition, int offset) { mRowHeaderLayoutManager.scrollToPositionWithOffset(rowPosition, offset); mCellLayoutManager.scrollToPositionWithOffset(rowPosition, offset); } private void scrollCellHorizontally(int columnPosition, int offset) { CellLayoutManager cellLayoutManager = mTableView.getCellLayoutManager(); for (int i = cellLayoutManager.findFirstVisibleItemPosition(); i < cellLayoutManager .findLastVisibleItemPosition() + 1; i++) { RecyclerView cellRowRecyclerView = (RecyclerView) cellLayoutManager .findViewByPosition(i); if (cellRowRecyclerView != null) { ColumnLayoutManager columnLayoutManager = (ColumnLayoutManager) cellRowRecyclerView.getLayoutManager(); columnLayoutManager.scrollToPositionWithOffset(columnPosition, offset); } } } private void scrollColumnHeader(int columnPosition, int offset) { mTableView.getColumnHeaderLayoutManager().scrollToPositionWithOffset(columnPosition, offset); } public int getColumnPosition() { return mColumnHeaderLayoutManager.findFirstVisibleItemPosition(); } public int getColumnPositionOffset() { View child = mColumnHeaderLayoutManager.findViewByPosition(mColumnHeaderLayoutManager .findFirstVisibleItemPosition()); if (child != null) { return child.getLeft(); } return 0; } public int getRowPosition() { return mRowHeaderLayoutManager.findFirstVisibleItemPosition(); } public int getRowPositionOffset() { View child = mRowHeaderLayoutManager.findViewByPosition(mRowHeaderLayoutManager .findFirstVisibleItemPosition()); if (child != null) { return child.getLeft(); } return 0; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/handler/SelectionHandler.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.handler; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerView; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder.SelectionState; import com.evrencoskun.tableview.layoutmanager.CellLayoutManager; /** * Created by evrencoskun on 24/10/2017. */ public class SelectionHandler { public static final int UNSELECTED_POSITION = -1; private int mSelectedRowPosition = UNSELECTED_POSITION; private int mSelectedColumnPosition = UNSELECTED_POSITION; private boolean shadowEnabled = true; @NonNull private final ITableView mTableView; private AbstractViewHolder mPreviousSelectedViewHolder; @NonNull private final CellRecyclerView mColumnHeaderRecyclerView; @NonNull private final CellRecyclerView mRowHeaderRecyclerView; @NonNull private final CellLayoutManager mCellLayoutManager; public SelectionHandler(@NonNull ITableView tableView) { this.mTableView = tableView; this.mColumnHeaderRecyclerView = mTableView.getColumnHeaderRecyclerView(); this.mRowHeaderRecyclerView = mTableView.getRowHeaderRecyclerView(); this.mCellLayoutManager = mTableView.getCellLayoutManager(); } public boolean isShadowEnabled() { return shadowEnabled; } public void setShadowEnabled(boolean shadowEnabled) { this.shadowEnabled = shadowEnabled; } public void setSelectedCellPositions(@Nullable AbstractViewHolder selectedViewHolder, int column, int row) { this.setPreviousSelectedView(selectedViewHolder); this.mSelectedColumnPosition = column; this.mSelectedRowPosition = row; if (shadowEnabled) { selectedCellView(); } } public void setSelectedColumnPosition(@Nullable AbstractViewHolder selectedViewHolder, int column) { this.setPreviousSelectedView(selectedViewHolder); this.mSelectedColumnPosition = column; selectedColumnHeader(); // Set unselected others mSelectedRowPosition = UNSELECTED_POSITION; } public int getSelectedColumnPosition() { return mSelectedColumnPosition; } public void setSelectedRowPosition(@Nullable AbstractViewHolder selectedViewHolder, int row) { this.setPreviousSelectedView(selectedViewHolder); this.mSelectedRowPosition = row; selectedRowHeader(); // Set unselected others mSelectedColumnPosition = UNSELECTED_POSITION; } public int getSelectedRowPosition() { return mSelectedRowPosition; } public void setPreviousSelectedView(@Nullable AbstractViewHolder viewHolder) { restorePreviousSelectedView(); if (mPreviousSelectedViewHolder != null) { // Change color mPreviousSelectedViewHolder.setBackgroundColor(mTableView.getUnSelectedColor()); // Change state mPreviousSelectedViewHolder.setSelected(SelectionState.UNSELECTED); } AbstractViewHolder oldViewHolder = mCellLayoutManager.getCellViewHolder (getSelectedColumnPosition(), getSelectedRowPosition()); if (oldViewHolder != null) { // Change color oldViewHolder.setBackgroundColor(mTableView.getUnSelectedColor()); // Change state oldViewHolder.setSelected(SelectionState.UNSELECTED); } this.mPreviousSelectedViewHolder = viewHolder; // Change color mPreviousSelectedViewHolder.setBackgroundColor(mTableView.getSelectedColor()); // Change state mPreviousSelectedViewHolder.setSelected(SelectionState.SELECTED); } private void restorePreviousSelectedView() { if (mSelectedColumnPosition != UNSELECTED_POSITION && mSelectedRowPosition != UNSELECTED_POSITION) { unselectedCellView(); } else if (mSelectedColumnPosition != UNSELECTED_POSITION) { unselectedColumnHeader(); } else if (mSelectedRowPosition != UNSELECTED_POSITION) { unselectedRowHeader(); } } private void selectedRowHeader() { // Change background color of the selected cell views changeVisibleCellViewsBackgroundForRow(mSelectedRowPosition, true); // Change background color of the column headers to be shown as a shadow. if (shadowEnabled) { changeSelectionOfRecyclerView(mColumnHeaderRecyclerView, SelectionState.SHADOWED, mTableView.getShadowColor()); } } private void unselectedRowHeader() { changeVisibleCellViewsBackgroundForRow(mSelectedRowPosition, false); // Change background color of the column headers to be shown as a normal. changeSelectionOfRecyclerView(mColumnHeaderRecyclerView, SelectionState.UNSELECTED, mTableView.getUnSelectedColor()); } private void selectedCellView() { int shadowColor = mTableView.getShadowColor(); // Change background color of the row header which is located on Y Position of the cell // view. AbstractViewHolder rowHeader = (AbstractViewHolder) mRowHeaderRecyclerView .findViewHolderForAdapterPosition(mSelectedRowPosition); // If view is null, that means the row view holder was already recycled. if (rowHeader != null) { // Change color rowHeader.setBackgroundColor(shadowColor); // Change state rowHeader.setSelected(SelectionState.SHADOWED); } // Change background color of the column header which is located on X Position of the cell // view. AbstractViewHolder columnHeader = (AbstractViewHolder) mColumnHeaderRecyclerView .findViewHolderForAdapterPosition(mSelectedColumnPosition); if (columnHeader != null) { // Change color columnHeader.setBackgroundColor(shadowColor); // Change state columnHeader.setSelected(SelectionState.SHADOWED); } } private void unselectedCellView() { int unSelectedColor = mTableView.getUnSelectedColor(); // Change background color of the row header which is located on Y Position of the cell // view. AbstractViewHolder rowHeader = (AbstractViewHolder) mRowHeaderRecyclerView .findViewHolderForAdapterPosition(mSelectedRowPosition); // If view is null, that means the row view holder was already recycled. if (rowHeader != null) { // Change color rowHeader.setBackgroundColor(unSelectedColor); // Change state rowHeader.setSelected(SelectionState.UNSELECTED); } // Change background color of the column header which is located on X Position of the cell // view. AbstractViewHolder columnHeader = (AbstractViewHolder) mColumnHeaderRecyclerView .findViewHolderForAdapterPosition(mSelectedColumnPosition); if (columnHeader != null) { // Change color columnHeader.setBackgroundColor(unSelectedColor); // Change state columnHeader.setSelected(SelectionState.UNSELECTED); } } private void selectedColumnHeader() { changeVisibleCellViewsBackgroundForColumn(mSelectedColumnPosition, true); changeSelectionOfRecyclerView(mRowHeaderRecyclerView, SelectionState.SHADOWED, mTableView .getShadowColor()); } private void unselectedColumnHeader() { changeVisibleCellViewsBackgroundForColumn(mSelectedColumnPosition, false); changeSelectionOfRecyclerView(mRowHeaderRecyclerView, SelectionState.UNSELECTED, mTableView.getUnSelectedColor()); } public boolean isCellSelected(int column, int row) { return (getSelectedColumnPosition() == column && getSelectedRowPosition() == row) || isColumnSelected(column) || isRowSelected(row); } @NonNull public SelectionState getCellSelectionState(int column, int row) { if (isCellSelected(column, row)) { return SelectionState.SELECTED; } return SelectionState.UNSELECTED; } public boolean isColumnSelected(int column) { return (getSelectedColumnPosition() == column && getSelectedRowPosition() == UNSELECTED_POSITION); } public boolean isColumnShadowed(int column) { return (getSelectedColumnPosition() == column && getSelectedRowPosition() != UNSELECTED_POSITION) || (getSelectedColumnPosition() == UNSELECTED_POSITION && getSelectedRowPosition() != UNSELECTED_POSITION); } public boolean isAnyColumnSelected() { return (getSelectedColumnPosition() != SelectionHandler.UNSELECTED_POSITION && getSelectedRowPosition() == SelectionHandler.UNSELECTED_POSITION); } @NonNull public SelectionState getColumnSelectionState(int column) { if (isColumnShadowed(column)) { return SelectionState.SHADOWED; } else if (isColumnSelected(column)) { return SelectionState.SELECTED; } else { return SelectionState.UNSELECTED; } } public boolean isRowSelected(int row) { return (getSelectedRowPosition() == row && getSelectedColumnPosition() == UNSELECTED_POSITION); } public boolean isRowShadowed(int row) { return (getSelectedRowPosition() == row && getSelectedColumnPosition() != UNSELECTED_POSITION) || (getSelectedRowPosition() == UNSELECTED_POSITION && getSelectedColumnPosition() != UNSELECTED_POSITION); } @NonNull public SelectionState getRowSelectionState(int row) { if (isRowShadowed(row)) { return SelectionState.SHADOWED; } else if (isRowSelected(row)) { return SelectionState.SELECTED; } else { return SelectionState.UNSELECTED; } } private void changeVisibleCellViewsBackgroundForRow(int row, boolean isSelected) { int backgroundColor = mTableView.getUnSelectedColor(); SelectionState selectionState = SelectionState.UNSELECTED; if (isSelected) { backgroundColor = mTableView.getSelectedColor(); selectionState = SelectionState.SELECTED; } CellRecyclerView recyclerView = (CellRecyclerView) mCellLayoutManager.findViewByPosition (row); if (recyclerView == null) { return; } changeSelectionOfRecyclerView(recyclerView, selectionState, backgroundColor); } private void changeVisibleCellViewsBackgroundForColumn(int column, boolean isSelected) { int backgroundColor = mTableView.getUnSelectedColor(); SelectionState selectionState = SelectionState.UNSELECTED; if (isSelected) { backgroundColor = mTableView.getSelectedColor(); selectionState = SelectionState.SELECTED; } // Get visible Cell ViewHolders by Column Position for (int i = mCellLayoutManager.findFirstVisibleItemPosition(); i < mCellLayoutManager .findLastVisibleItemPosition() + 1; i++) { CellRecyclerView cellRowRecyclerView = (CellRecyclerView) mCellLayoutManager .findViewByPosition(i); AbstractViewHolder holder = (AbstractViewHolder) cellRowRecyclerView .findViewHolderForAdapterPosition(column); if (holder != null) { // Get each view container of the cell view and set unselected color. holder.setBackgroundColor(backgroundColor); // Change selection status of the view holder holder.setSelected(selectionState); } } } public void changeRowBackgroundColorBySelectionStatus(@NonNull AbstractViewHolder viewHolder, @NonNull SelectionState selectionState) { if (shadowEnabled && selectionState == SelectionState.SHADOWED) { viewHolder.setBackgroundColor(mTableView.getShadowColor()); } else if (selectionState == SelectionState.SELECTED) { viewHolder.setBackgroundColor(mTableView.getSelectedColor()); } else { viewHolder.setBackgroundColor(mTableView.getUnSelectedColor()); } } public void changeColumnBackgroundColorBySelectionStatus(@NonNull AbstractViewHolder viewHolder, @NonNull SelectionState selectionState) { if (shadowEnabled && selectionState == SelectionState.SHADOWED) { viewHolder.setBackgroundColor(mTableView.getShadowColor()); } else if (selectionState == SelectionState.SELECTED) { viewHolder.setBackgroundColor(mTableView.getSelectedColor()); } else { viewHolder.setBackgroundColor(mTableView.getUnSelectedColor()); } } public void changeSelectionOfRecyclerView(CellRecyclerView recyclerView, @NonNull AbstractViewHolder .SelectionState selectionState, @ColorInt int backgroundColor) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView .getLayoutManager(); for (int i = linearLayoutManager.findFirstVisibleItemPosition(); i < linearLayoutManager .findLastVisibleItemPosition() + 1; i++) { AbstractViewHolder viewHolder = (AbstractViewHolder) recyclerView .findViewHolderForAdapterPosition(i); if (viewHolder != null) { if (!mTableView.isIgnoreSelectionColors()) { // Change background color viewHolder.setBackgroundColor(backgroundColor); } // Change selection status of the view holder viewHolder.setSelected(selectionState); } } } public void clearSelection() { unselectedRowHeader(); unselectedCellView(); unselectedColumnHeader(); } public void setSelectedRowPosition(int row) { this.mSelectedRowPosition = row; } public void setSelectedColumnPosition(int column) { this.mSelectedColumnPosition = column; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/handler/VisibilityHandler.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.handler; import android.util.Log; import android.util.SparseArray; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.AbstractTableAdapter; import java.util.List; /** * Created by evrencoskun on 24.12.2017. */ public class VisibilityHandler { private static final String LOG_TAG = VisibilityHandler.class.getSimpleName(); @NonNull private final ITableView mTableView; @NonNull private SparseArray mHideRowList = new SparseArray<>(); @NonNull private SparseArray mHideColumnList = new SparseArray<>(); public VisibilityHandler(@NonNull ITableView tableView) { this.mTableView = tableView; } public void hideRow(int row) { int viewRow = convertIndexToViewIndex(row, mHideRowList); if (mHideRowList.get(row) == null) { // add row the list mHideRowList.put(row, getRowValueFromPosition(row)); // remove row model from adapter mTableView.getAdapter().removeRow(viewRow); } else { Log.e(LOG_TAG, "This row is already hidden."); } } public void showRow(int row) { showRow(row, true); } private void showRow(int row, boolean removeFromList) { Row hiddenRow = mHideRowList.get(row); if (hiddenRow != null) { // add row model to the adapter mTableView.getAdapter().addRow(row, hiddenRow.getRowHeaderModel(), hiddenRow.getCellModelList()); } else { Log.e(LOG_TAG, "This row is already visible."); } if (removeFromList) { mHideRowList.remove(row); } } public void clearHideRowList() { mHideRowList.clear(); } public void showAllHiddenRows() { for (int i = 0; i < mHideRowList.size(); i++) { int row = mHideRowList.keyAt(i); showRow(row, false); } clearHideRowList(); } public boolean isRowVisible(int row) { return mHideRowList.get(row) == null; } public void hideColumn(int column) { int viewColumn = convertIndexToViewIndex(column, mHideColumnList); if (mHideColumnList.get(column) == null) { // add column the list mHideColumnList.put(column, getColumnValueFromPosition(column)); // remove row model from adapter mTableView.getAdapter().removeColumn(viewColumn); } else { Log.e(LOG_TAG, "This column is already hidden."); } } public void showColumn(int column) { showColumn(column, true); } private void showColumn(int column, boolean removeFromList) { Column hiddenColumn = mHideColumnList.get(column); if (hiddenColumn != null) { // add column model to the adapter mTableView.getAdapter().addColumn(column, hiddenColumn.getColumnHeaderModel(), hiddenColumn.getCellModelList()); } else { Log.e(LOG_TAG, "This column is already visible."); } if (removeFromList) { mHideColumnList.remove(column); } } public void clearHideColumnList() { mHideColumnList.clear(); } public void showAllHiddenColumns() { for (int i = 0; i < mHideColumnList.size(); i++) { int column = mHideColumnList.keyAt(i); showColumn(column, false); } clearHideColumnList(); } public boolean isColumnVisible(int column) { return mHideColumnList.get(column) == null; } /** * Hiding row and column process needs to consider the hidden rows or columns with a smaller * index to be able hide the correct index. * * @param index, stands for column or row index. * @param list, stands for HideRowList or HideColumnList */ private int getSmallerHiddenCount(int index, SparseArray list) { int count = 0; for (int i = 0; i < index && i < list.size(); i++) { if (list.valueAt(i) != null) { count++; } } return count; } /** * It converts model index to View index considering the previous hidden rows or columns. So, * when we add or remove any item of RecyclerView, we need to view index. */ private int convertIndexToViewIndex(int index, SparseArray list) { return index - getSmallerHiddenCount(index, list); } static class Row { private final int mYPosition; @Nullable private final Object mRowHeaderModel; @Nullable private final List mCellModelList; public Row(int row, @Nullable Object rowHeaderModel, @Nullable List cellModelList) { this.mYPosition = row; this.mRowHeaderModel = rowHeaderModel; this.mCellModelList = cellModelList; } public int getYPosition() { return mYPosition; } @Nullable public Object getRowHeaderModel() { return mRowHeaderModel; } @Nullable public List getCellModelList() { return mCellModelList; } } static class Column { private final int mYPosition; @Nullable private final Object mColumnHeaderModel; @NonNull private final List mCellModelList; public Column(int yPosition, @Nullable Object columnHeaderModel, @NonNull List cellModelList) { this.mYPosition = yPosition; this.mColumnHeaderModel = columnHeaderModel; this.mCellModelList = cellModelList; } public int getYPosition() { return mYPosition; } @Nullable public Object getColumnHeaderModel() { return mColumnHeaderModel; } @NonNull public List getCellModelList() { return mCellModelList; } } @NonNull private Row getRowValueFromPosition(int row) { AbstractTableAdapter adapter = mTableView.getAdapter(); Object rowHeaderModel = adapter.getRowHeaderItem(row); List cellModelList = adapter.getCellRowItems(row); return new Row(row, rowHeaderModel, cellModelList); } @NonNull private Column getColumnValueFromPosition(int column) { AbstractTableAdapter adapter = mTableView.getAdapter(); Object columnHeaderModel = adapter.getColumnHeaderItem(column); List cellModelList = adapter.getCellColumnItems(column); return new Column(column, columnHeaderModel, cellModelList); } @NonNull public SparseArray getHideRowList() { return mHideRowList; } @NonNull public SparseArray getHideColumnList() { return mHideColumnList; } public void setHideRowList(@NonNull SparseArray rowList) { this.mHideRowList = rowList; } public void setHideColumnList(@NonNull SparseArray columnList) { this.mHideColumnList = columnList; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/layoutmanager/CellLayoutManager.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.layoutmanager; import android.content.Context; import android.os.Handler; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerView; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.listener.scroll.HorizontalRecyclerViewListener; import com.evrencoskun.tableview.util.TableViewUtils; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; /** * Created by evrencoskun on 24/06/2017. */ public class CellLayoutManager extends LinearLayoutManager { private static final String LOG_TAG = CellLayoutManager.class.getSimpleName(); private static final int IGNORE_LEFT = -99999; @NonNull private final ColumnHeaderLayoutManager mColumnHeaderLayoutManager; @NonNull private final CellRecyclerView mRowHeaderRecyclerView; private HorizontalRecyclerViewListener mHorizontalListener; @NonNull private final ITableView mTableView; @NonNull private final SparseArray mCachedWidthList = new SparseArray<>(); //TODO: Store a single instance for both cell and column cache width values. private int mLastDy = 0; private boolean mNeedSetLeft; private boolean mNeedFit; public CellLayoutManager(@NonNull Context context, @NonNull ITableView tableView) { super(context); this.mTableView = tableView; this.mColumnHeaderLayoutManager = tableView.getColumnHeaderLayoutManager(); this.mRowHeaderRecyclerView = tableView.getRowHeaderRecyclerView(); initialize(); } private void initialize() { this.setOrientation(VERTICAL); // Add new one } @Override public void onAttachedToWindow(RecyclerView view) { super.onAttachedToWindow(view); // initialize the instances if (mHorizontalListener == null) { mHorizontalListener = mTableView.getHorizontalRecyclerViewListener(); } } @Override public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { if (mRowHeaderRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE && !mRowHeaderRecyclerView.isScrollOthers()) { // CellRecyclerViews should be scrolled after the RowHeaderRecyclerView. // Because it is one of the main compared criterion to make each columns fit. mRowHeaderRecyclerView.scrollBy(0, dy); } int scroll = super.scrollVerticallyBy(dy, recycler, state); // It is important to determine right position to fit all columns which are the same y pos. mLastDy = dy; return scroll; } @Override public void onScrollStateChanged(int state) { super.onScrollStateChanged(state); if (state == RecyclerView.SCROLL_STATE_IDLE) { // It is important to set it 0 to be able to know which direction is being scrolled mLastDy = 0; } } /** * This method helps to fit all columns which are displayed on screen. * Especially it will be called when TableView is scrolled on vertically. */ public void fitWidthSize(boolean scrollingUp) { int left = mColumnHeaderLayoutManager.getFirstItemLeft(); for (int i = mColumnHeaderLayoutManager.findFirstVisibleItemPosition(); i < mColumnHeaderLayoutManager.findLastVisibleItemPosition() + 1; i++) { left = fitSize(i, left, scrollingUp); } mNeedSetLeft = false; } /** * This method helps to fit a column. it will be called when TableView is scrolled on * horizontally. */ public void fitWidthSize(int position, boolean scrollingLeft) { fitSize(position, IGNORE_LEFT, false); if (mNeedSetLeft & scrollingLeft) { // Works just like invoke later of swing utils. Handler handler = new Handler(); handler.post(() -> fitWidthSize2(true)); } } private int fitSize(int position, int left, boolean scrollingUp) { int cellRight = -1; int columnCacheWidth = mColumnHeaderLayoutManager.getCacheWidth(position); View column = mColumnHeaderLayoutManager.findViewByPosition(position); if (column != null) { // Determine default right cellRight = column.getLeft() + columnCacheWidth + 1; if (scrollingUp) { // Loop reverse order for (int i = findLastVisibleItemPosition(); i >= findFirstVisibleItemPosition(); i--) { cellRight = fit(position, i, left, cellRight, columnCacheWidth); } } else { // Loop for all rows which are visible. for (int j = findFirstVisibleItemPosition(); j < findLastVisibleItemPosition() + 1; j++) { cellRight = fit(position, j, left, cellRight, columnCacheWidth); } } } else { Log.e(LOG_TAG, "Warning: column couldn't found for " + position); } return cellRight; } private int fit(int xPosition, int yPosition, int left, int right, int columnCachedWidth) { CellRecyclerView child = (CellRecyclerView) findViewByPosition(yPosition); if (child != null) { ColumnLayoutManager childLayoutManager = (ColumnLayoutManager) child.getLayoutManager(); int cellCacheWidth = getCacheWidth(yPosition, xPosition); View cell = childLayoutManager.findViewByPosition(xPosition); // Control whether the cell needs to be fitted by column header or not. if (cell != null) { if (cellCacheWidth != columnCachedWidth || mNeedSetLeft) { // This is just for setting width value if (cellCacheWidth != columnCachedWidth) { cellCacheWidth = columnCachedWidth; TableViewUtils.setWidth(cell, cellCacheWidth); setCacheWidth(yPosition, xPosition, cellCacheWidth); } // Even if the cached values are same, the left & right value wouldn't change. // mNeedSetLeft & the below lines for it. if (left != IGNORE_LEFT && cell.getLeft() != left) { // Calculate scroll distance int scrollX = Math.max(cell.getLeft(), left) - Math.min(cell.getLeft(), left); // Update its left cell.setLeft(left); int offset = mHorizontalListener.getScrollPositionOffset(); // It shouldn't be scroll horizontally and the problem is gotten just for // first visible item. if (offset > 0 && xPosition == childLayoutManager .findFirstVisibleItemPosition() && getCellRecyclerViewScrollState() != RecyclerView.SCROLL_STATE_IDLE) { int scrollPosition = mHorizontalListener.getScrollPosition(); offset = mHorizontalListener.getScrollPositionOffset() + scrollX; // Update scroll position offset value mHorizontalListener.setScrollPositionOffset(offset); // Scroll considering to the desired value. childLayoutManager.scrollToPositionWithOffset(scrollPosition, offset); } } if (cell.getWidth() != cellCacheWidth) { if (left != IGNORE_LEFT) { // TODO: + 1 is for decoration item. It should be gotten from a // generic method of layoutManager // Set right right = cell.getLeft() + cellCacheWidth + 1; cell.setRight(right); childLayoutManager.layoutDecoratedWithMargins(cell, cell.getLeft(), cell.getTop(), cell.getRight(), cell.getBottom()); } mNeedSetLeft = true; } } } } return right; } /** * Alternative method of fitWidthSize(). * The main difference is this method works after main thread draw the ui components. */ public void fitWidthSize2(boolean scrollingLeft) { // The below line helps to change left & right value of the each column // header views // without using requestLayout(). mColumnHeaderLayoutManager.customRequestLayout(); // Get the right scroll position information from Column header RecyclerView int columnHeaderScrollPosition = mTableView.getColumnHeaderRecyclerView().getScrolledX(); int columnHeaderOffset = mColumnHeaderLayoutManager.getFirstItemLeft(); int columnHeaderFirstItem = mColumnHeaderLayoutManager.findFirstVisibleItemPosition(); // Fit all visible columns widths for (int i = mColumnHeaderLayoutManager.findFirstVisibleItemPosition(); i < mColumnHeaderLayoutManager.findLastVisibleItemPosition() + 1; i++) { fitSize2(i, scrollingLeft, columnHeaderScrollPosition, columnHeaderOffset, columnHeaderFirstItem); } mNeedSetLeft = false; } /** * Alternative method of fitWidthSize(). * The main difference is this method works after main thread draw the ui components. */ public void fitWidthSize2(int position, boolean scrollingLeft) { // The below line helps to change left & right value of the each column // header views // without using requestLayout(). mColumnHeaderLayoutManager.customRequestLayout(); // Get the right scroll position information from Column header RecyclerView int columnHeaderScrollPosition = mTableView.getColumnHeaderRecyclerView().getScrolledX(); int columnHeaderOffset = mColumnHeaderLayoutManager.getFirstItemLeft(); int columnHeaderFirstItem = mColumnHeaderLayoutManager.findFirstVisibleItemPosition(); // Fit all visible columns widths fitSize2(position, scrollingLeft, columnHeaderScrollPosition, columnHeaderOffset, columnHeaderFirstItem); mNeedSetLeft = false; } private void fitSize2(int position, boolean scrollingLeft, int columnHeaderScrollPosition, int columnHeaderOffset, int columnHeaderFirstItem) { int columnCacheWidth = mColumnHeaderLayoutManager.getCacheWidth(position); View column = mColumnHeaderLayoutManager.findViewByPosition(position); if (column != null) { // Loop for all rows which are visible. for (int j = findFirstVisibleItemPosition(); j < findLastVisibleItemPosition() + 1; j++) { // Get CellRowRecyclerView CellRecyclerView child = (CellRecyclerView) findViewByPosition(j); if (child != null) { ColumnLayoutManager childLayoutManager = (ColumnLayoutManager) child .getLayoutManager(); // Checking Scroll position is necessary. Because, even if they have same width // values, their scroll positions can be different. if (!scrollingLeft && columnHeaderScrollPosition != child.getScrolledX()) { // Column Header RecyclerView has the right scroll position. So, // considering it childLayoutManager.scrollToPositionWithOffset(columnHeaderFirstItem, columnHeaderOffset); } if (childLayoutManager != null) { fit2(position, j, columnCacheWidth, column, childLayoutManager); } } } } } private void fit2(int xPosition, int yPosition, int columnCachedWidth, @NonNull View column, @NonNull ColumnLayoutManager childLayoutManager) { int cellCacheWidth = getCacheWidth(yPosition, xPosition); View cell = childLayoutManager.findViewByPosition(xPosition); // Control whether the cell needs to be fitted by column header or not. if (cell != null) { if (cellCacheWidth != columnCachedWidth || mNeedSetLeft) { // This is just for setting width value if (cellCacheWidth != columnCachedWidth) { cellCacheWidth = columnCachedWidth; TableViewUtils.setWidth(cell, cellCacheWidth); setCacheWidth(yPosition, xPosition, cellCacheWidth); } // The left & right values of Column header can be considered. Because this // method will be worked // after drawing process of main thread. if (column.getLeft() != cell.getLeft() || column.getRight() != cell.getRight()) { // TODO: + 1 is for decoration item. It should be gotten from a generic // method of layoutManager // Set right & left values cell.setLeft(column.getLeft()); cell.setRight(column.getRight() + 1); childLayoutManager.layoutDecoratedWithMargins(cell, cell.getLeft(), cell .getTop(), cell.getRight(), cell.getBottom()); mNeedSetLeft = true; } } } } public boolean shouldFitColumns(int yPosition) { // Scrolling horizontally if (getCellRecyclerViewScrollState() == RecyclerView.SCROLL_STATE_IDLE) { int lastVisiblePosition = findLastVisibleItemPosition(); CellRecyclerView lastCellRecyclerView = (CellRecyclerView) findViewByPosition (lastVisiblePosition); if (lastCellRecyclerView != null) { if (yPosition == lastVisiblePosition) { return true; } else if (lastCellRecyclerView.isScrollOthers() && yPosition == lastVisiblePosition - 1) { return true; } } } return false; } @Override public void measureChildWithMargins(@NonNull View child, int widthUsed, int heightUsed) { super.measureChildWithMargins(child, widthUsed, heightUsed); // If has fixed width is true, than calculation of the column width is not necessary. if (mTableView.hasFixedWidth()) { return; } int position = getPosition(child); ColumnLayoutManager childLayoutManager = (ColumnLayoutManager) ((CellRecyclerView) child) .getLayoutManager(); // the below codes should be worked when it is scrolling vertically if (getCellRecyclerViewScrollState() != RecyclerView.SCROLL_STATE_IDLE) { if (childLayoutManager.isNeedFit()) { // Scrolling up if (mLastDy < 0) { Log.e(LOG_TAG, position + " fitWidthSize all vertically up"); fitWidthSize(true); } else { // Scrolling down Log.e(LOG_TAG, position + " fitWidthSize all vertically down"); fitWidthSize(false); } // all columns have been fitted. childLayoutManager.clearNeedFit(); } // Set the right initialPrefetch size to improve performance childLayoutManager.setInitialPrefetchItemCount(childLayoutManager.getChildCount()); // That means,populating for the first time like fetching all data to display. // It shouldn't be worked when it is scrolling horizontally ."getLastDx() == 0" // control for it. } else if (childLayoutManager.getLastDx() == 0 && getCellRecyclerViewScrollState() == RecyclerView.SCROLL_STATE_IDLE) { if (childLayoutManager.isNeedFit()) { mNeedFit = true; // all columns have been fitted. childLayoutManager.clearNeedFit(); } if (mNeedFit) { // for the first time to populate adapter if (mTableView.getRowHeaderLayoutManager().findLastVisibleItemPosition() == position) { fitWidthSize2(false); Log.e(LOG_TAG, position + " fitWidthSize populating data for the first time"); mNeedFit = false; } } } } @NonNull public AbstractViewHolder[] getVisibleCellViewsByColumnPosition(int xPosition) { int visibleChildCount = findLastVisibleItemPosition() - findFirstVisibleItemPosition() + 1; int index = 0; AbstractViewHolder[] viewHolders = new AbstractViewHolder[visibleChildCount]; for (int i = findFirstVisibleItemPosition(); i < findLastVisibleItemPosition() + 1; i++) { CellRecyclerView cellRowRecyclerView = (CellRecyclerView) findViewByPosition(i); AbstractViewHolder holder = (AbstractViewHolder) cellRowRecyclerView .findViewHolderForAdapterPosition(xPosition); viewHolders[index] = holder; index++; } return viewHolders; } @Nullable public AbstractViewHolder getCellViewHolder(int xPosition, int yPosition) { CellRecyclerView cellRowRecyclerView = (CellRecyclerView) findViewByPosition(yPosition); if (cellRowRecyclerView != null) { return (AbstractViewHolder) cellRowRecyclerView.findViewHolderForAdapterPosition (xPosition); } return null; } public void remeasureAllChild() { // TODO: the below code causes requestLayout() improperly called by com.evrencoskun.tableview.adapter for (int j = 0; j < getChildCount(); j++) { CellRecyclerView recyclerView = (CellRecyclerView) getChildAt(j); recyclerView.getLayoutParams().width = WRAP_CONTENT; recyclerView.requestLayout(); } } /** * Allows to set cache width value for single cell item. */ public void setCacheWidth(int row, int column, int width) { SparseIntArray cellRowCache = mCachedWidthList.get(row); if (cellRowCache == null) { cellRowCache = new SparseIntArray(); } cellRowCache.put(column, width); mCachedWidthList.put(row, cellRowCache); } /** * Allows to set cache width value for all cell items that is located on column position. */ public void setCacheWidth(int column, int width) { for (int i = 0; i < mRowHeaderRecyclerView.getAdapter().getItemCount(); i++) { // set cache width for single cell item. setCacheWidth(i, column, width); } } public int getCacheWidth(int row, int column) { SparseIntArray cellRowCaches = mCachedWidthList.get(row); if (cellRowCaches != null) { return cellRowCaches.get(column, -1); } return -1; } /** * Clears the widths which have been calculated and reused. */ public void clearCachedWidths() { mCachedWidthList.clear(); } @NonNull public CellRecyclerView[] getVisibleCellRowRecyclerViews() { int length = findLastVisibleItemPosition() - findFirstVisibleItemPosition() + 1; CellRecyclerView[] recyclerViews = new CellRecyclerView[length]; int index = 0; for (int i = findFirstVisibleItemPosition(); i < findLastVisibleItemPosition() + 1; i++) { recyclerViews[index] = (CellRecyclerView) findViewByPosition(i); index++; } return recyclerViews; } private int getCellRecyclerViewScrollState() { return mTableView.getCellRecyclerView().getScrollState(); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/layoutmanager/ColumnHeaderLayoutManager.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.layoutmanager; import android.content.Context; import android.util.SparseIntArray; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.util.TableViewUtils; /** * Created by evrencoskun on 30/07/2017. */ public class ColumnHeaderLayoutManager extends LinearLayoutManager { //private SparseArray mCachedWidthList; @NonNull private final SparseIntArray mCachedWidthList = new SparseIntArray(); @NonNull private final ITableView mTableView; public ColumnHeaderLayoutManager(@NonNull Context context, @NonNull ITableView tableView) { super(context); mTableView = tableView; this.setOrientation(ColumnHeaderLayoutManager.HORIZONTAL); } @Override public void measureChildWithMargins(@NonNull View child, int widthUsed, int heightUsed) { super.measureChildWithMargins(child, widthUsed, heightUsed); // If has fixed width is true, than calculation of the column width is not necessary. if (mTableView.hasFixedWidth()) { return; } measureChild(child, widthUsed, heightUsed); } @Override public void measureChild(@NonNull View child, int widthUsed, int heightUsed) { // If has fixed width is true, than calculation of the column width is not necessary. if (mTableView.hasFixedWidth()) { super.measureChild(child, widthUsed, heightUsed); return; } int position = getPosition(child); int cacheWidth = getCacheWidth(position); // If the width value of the cell has already calculated, then set the value if (cacheWidth != -1) { TableViewUtils.setWidth(child, cacheWidth); } else { super.measureChild(child, widthUsed, heightUsed); } } public void setCacheWidth(int position, int width) { mCachedWidthList.put(position, width); } public int getCacheWidth(int position) { return mCachedWidthList.get(position, -1); } public int getFirstItemLeft() { View firstColumnHeader = findViewByPosition(findFirstVisibleItemPosition()); return firstColumnHeader.getLeft(); } /** * Helps to recalculate the width value of the cell that is located in given position. */ public void removeCachedWidth(int position) { mCachedWidthList.removeAt(position); } /** * Clears the widths which have been calculated and reused. */ public void clearCachedWidths() { mCachedWidthList.clear(); } public void customRequestLayout() { int left = getFirstItemLeft(); int right; for (int i = findFirstVisibleItemPosition(); i < findLastVisibleItemPosition() + 1; i++) { // Column headers should have been already calculated. right = left + getCacheWidth(i); View columnHeader = findViewByPosition(i); columnHeader.setLeft(left); columnHeader.setRight(right); layoutDecoratedWithMargins(columnHeader, columnHeader.getLeft(), columnHeader.getTop (), columnHeader.getRight(), columnHeader.getBottom()); // + 1 is for decoration item. left = right + 1; } } @NonNull public AbstractViewHolder[] getVisibleViewHolders() { int visibleChildCount = findLastVisibleItemPosition() - findFirstVisibleItemPosition() + 1; int index = 0; AbstractViewHolder[] views = new AbstractViewHolder[visibleChildCount]; for (int i = findFirstVisibleItemPosition(); i < findLastVisibleItemPosition() + 1; i++) { views[index] = (AbstractViewHolder) mTableView.getColumnHeaderRecyclerView() .findViewHolderForAdapterPosition(i); index++; } return views; } @Nullable public AbstractViewHolder getViewHolder(int xPosition) { return (AbstractViewHolder) mTableView.getColumnHeaderRecyclerView() .findViewHolderForAdapterPosition(xPosition); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/layoutmanager/ColumnLayoutManager.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.layoutmanager; import android.content.Context; import android.util.Log; import android.view.View; import androidx.annotation.NonNull; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerView; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.util.TableViewUtils; /** * Created by evrencoskun on 10/06/2017. */ public class ColumnLayoutManager extends LinearLayoutManager { private static final String LOG_TAG = ColumnLayoutManager.class.getSimpleName(); @NonNull private final ITableView mTableView; private CellRecyclerView mCellRowRecyclerView; @NonNull private final CellRecyclerView mColumnHeaderRecyclerView; @NonNull private final ColumnHeaderLayoutManager mColumnHeaderLayoutManager; @NonNull private final CellLayoutManager mCellLayoutManager; private boolean mNeedFitForVerticalScroll, mNeedFitForHorizontalScroll; private int mLastDx = 0; private int mYPosition; public ColumnLayoutManager(@NonNull Context context, @NonNull ITableView tableView) { super(context); this.mTableView = tableView; this.mColumnHeaderRecyclerView = mTableView.getColumnHeaderRecyclerView(); this.mColumnHeaderLayoutManager = mTableView.getColumnHeaderLayoutManager(); this.mCellLayoutManager = mTableView.getCellLayoutManager(); // Set default orientation this.setOrientation(ColumnLayoutManager.HORIZONTAL); //If you are using a RecyclerView.RecycledViewPool, it might be a good idea to set this // flag to true so that views will be available to other RecyclerViews immediately. this.setRecycleChildrenOnDetach(true); } @Override public void onAttachedToWindow(RecyclerView view) { super.onAttachedToWindow(view); mCellRowRecyclerView = (CellRecyclerView) view; mYPosition = getRowPosition(); } @Override public void measureChildWithMargins(@NonNull View child, int widthUsed, int heightUsed) { super.measureChildWithMargins(child, widthUsed, heightUsed); // If has fixed width is true, than calculation of the column width is not necessary. if (mTableView.hasFixedWidth()) { return; } measureChild(child, widthUsed, heightUsed); } @Override public void measureChild(@NonNull View child, int widthUsed, int heightUsed) { int columnPosition = getPosition(child); // Get cached width size of column and cell int cacheWidth = mCellLayoutManager.getCacheWidth(mYPosition, columnPosition); int columnCacheWidth = mColumnHeaderLayoutManager.getCacheWidth(columnPosition); // Already each of them is same width size. if (cacheWidth != -1 && cacheWidth == columnCacheWidth) { // Control whether we need to set width or not. if (child.getMeasuredWidth() != cacheWidth) { TableViewUtils.setWidth(child, cacheWidth); } } else { View columnHeaderChild = mColumnHeaderLayoutManager.findViewByPosition(columnPosition); if (columnHeaderChild == null) { return; } // Need to calculate which one has the broadest width ? fitWidthSize(child, mYPosition, columnPosition, cacheWidth, columnCacheWidth, columnHeaderChild); } // Control all of the rows which has same column position. if (shouldFitColumns(columnPosition, mYPosition)) { if (mLastDx < 0) { Log.e(LOG_TAG, "x: " + columnPosition + " y: " + mYPosition + " fitWidthSize " + "left side "); mCellLayoutManager.fitWidthSize(columnPosition, true); } else { mCellLayoutManager.fitWidthSize(columnPosition, false); Log.e(LOG_TAG, "x: " + columnPosition + " y: " + mYPosition + " fitWidthSize " + "right side"); } mNeedFitForVerticalScroll = false; } // It need to be cleared to prevent unnecessary calculation. mNeedFitForHorizontalScroll = false; } private void fitWidthSize(@NonNull View child, int row, int column, int cellWidth, int columnHeaderWidth, @NonNull View columnHeaderChild) { if (cellWidth == -1) { // Alternatively, TableViewUtils.getWidth(child); cellWidth = child.getMeasuredWidth(); } if (columnHeaderWidth == -1) { // Alternatively, TableViewUtils.getWidth(columnHeaderChild) columnHeaderWidth = columnHeaderChild.getMeasuredWidth(); } if (cellWidth != 0) { if (columnHeaderWidth > cellWidth) { cellWidth = columnHeaderWidth; } else if (cellWidth > columnHeaderWidth) { columnHeaderWidth = cellWidth; } // Control whether column header needs to be change interns of width if (columnHeaderWidth != columnHeaderChild.getWidth()) { TableViewUtils.setWidth(columnHeaderChild, columnHeaderWidth); mNeedFitForVerticalScroll = true; mNeedFitForHorizontalScroll = true; } // Set the value to cache it for column header. mColumnHeaderLayoutManager.setCacheWidth(column, columnHeaderWidth); } // Set the width value to cache it for cell . TableViewUtils.setWidth(child, cellWidth); mCellLayoutManager.setCacheWidth(row, column, cellWidth); } private boolean shouldFitColumns(int xPosition, int yPosition) { if (mNeedFitForHorizontalScroll) { if (!mCellRowRecyclerView.isScrollOthers() && mCellLayoutManager.shouldFitColumns (yPosition)) { if (mLastDx > 0) { int last = findLastVisibleItemPosition(); //Log.e(LOG_TAG, "Warning: findFirstVisibleItemPosition is " + last); return xPosition == last; } else if (mLastDx < 0) { int first = findFirstVisibleItemPosition(); //Log.e(LOG_TAG, "Warning: findFirstVisibleItemPosition is " + first); return xPosition == first; } } } return false; } @Override public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) { if (mColumnHeaderRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE && mCellRowRecyclerView.isScrollOthers()) { // Every CellRowRecyclerViews should be scrolled after the ColumnHeaderRecyclerView. // Because it is the main compared one to make each columns fit. mColumnHeaderRecyclerView.scrollBy(dx, 0); } // It is important to determine the next attached view to fit all columns mLastDx = dx; // Set the right initialPrefetch size to improve performance this.setInitialPrefetchItemCount(2); return super.scrollHorizontallyBy(dx, recycler, state); } private int getRowPosition() { return mCellLayoutManager.getPosition(mCellRowRecyclerView); } public int getLastDx() { return mLastDx; } public boolean isNeedFit() { return mNeedFitForVerticalScroll; } public void clearNeedFit() { mNeedFitForVerticalScroll = false; } @NonNull public AbstractViewHolder[] getVisibleViewHolders() { int visibleChildCount = findLastVisibleItemPosition() - findFirstVisibleItemPosition() + 1; int index = 0; AbstractViewHolder[] views = new AbstractViewHolder[visibleChildCount]; for (int i = findFirstVisibleItemPosition(); i < findLastVisibleItemPosition() + 1; i++) { views[index] = (AbstractViewHolder) mCellRowRecyclerView .findViewHolderForAdapterPosition(i); index++; } return views; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/listener/ITableViewListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.listener; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; /** * Created by evrencoskun on 20/09/2017. */ public interface ITableViewListener { void onCellClicked(@NonNull RecyclerView.ViewHolder cellView, int column, int row); void onCellDoubleClicked(@NonNull RecyclerView.ViewHolder cellView, int column, int row); void onCellLongPressed(@NonNull RecyclerView.ViewHolder cellView, int column, int row); void onColumnHeaderClicked(@NonNull RecyclerView.ViewHolder columnHeaderView, int column); void onColumnHeaderDoubleClicked(@NonNull RecyclerView.ViewHolder columnHeaderView, int column); void onColumnHeaderLongPressed(@NonNull RecyclerView.ViewHolder columnHeaderView, int column); void onRowHeaderClicked(@NonNull RecyclerView.ViewHolder rowHeaderView, int row); void onRowHeaderDoubleClicked(@NonNull RecyclerView.ViewHolder rowHeaderView, int row); void onRowHeaderLongPressed(@NonNull RecyclerView.ViewHolder rowHeaderView, int row); } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/listener/SimpleTableViewListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.listener; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; /** * Convenience class to extend when you are only interested in a subset of events of {@link ITableViewListener}. */ public abstract class SimpleTableViewListener implements ITableViewListener { @Override public void onCellClicked(@NonNull RecyclerView.ViewHolder cellView, int column, int row) { } @Override public void onCellDoubleClicked(@NonNull RecyclerView.ViewHolder cellView, int column, int row) { } @Override public void onCellLongPressed(@NonNull RecyclerView.ViewHolder cellView, int column, int row) { } @Override public void onColumnHeaderClicked(@NonNull RecyclerView.ViewHolder columnHeaderView, int column) { } @Override public void onColumnHeaderDoubleClicked(@NonNull RecyclerView.ViewHolder columnHeaderView, int column) { } @Override public void onColumnHeaderLongPressed(@NonNull RecyclerView.ViewHolder columnHeaderView, int column) { } @Override public void onRowHeaderClicked(@NonNull RecyclerView.ViewHolder rowHeaderView, int row) { } @Override public void onRowHeaderDoubleClicked(@NonNull RecyclerView.ViewHolder rowHeaderView, int row) { } @Override public void onRowHeaderLongPressed(@NonNull RecyclerView.ViewHolder rowHeaderView, int row) { } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/listener/TableViewLayoutChangeListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.listener; import android.view.View; import androidx.annotation.NonNull; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerView; import com.evrencoskun.tableview.layoutmanager.CellLayoutManager; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; /** * Created by evrencoskun on 21.01.2018. */ public class TableViewLayoutChangeListener implements View.OnLayoutChangeListener { @NonNull private final CellRecyclerView mCellRecyclerView; @NonNull private final CellRecyclerView mColumnHeaderRecyclerView; @NonNull private final CellLayoutManager mCellLayoutManager; public TableViewLayoutChangeListener(@NonNull ITableView tableView) { this.mCellRecyclerView = tableView.getCellRecyclerView(); this.mColumnHeaderRecyclerView = tableView.getColumnHeaderRecyclerView(); this.mCellLayoutManager = tableView.getCellLayoutManager(); } @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { if (v.isShown() && (right - left) != (oldRight - oldLeft)) { // Control who need the remeasure if (mColumnHeaderRecyclerView.getWidth() > mCellRecyclerView.getWidth()) { // Remeasure all nested CellRow recyclerViews mCellLayoutManager.remeasureAllChild(); } else if (mCellRecyclerView.getWidth() > mColumnHeaderRecyclerView.getWidth()) { // It seems Column Header is needed. mColumnHeaderRecyclerView.getLayoutParams().width = WRAP_CONTENT; mColumnHeaderRecyclerView.requestLayout(); } } } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/listener/itemclick/AbstractItemClickListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.listener.itemclick; import android.view.GestureDetector; import android.view.MotionEvent; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerView; import com.evrencoskun.tableview.handler.SelectionHandler; import com.evrencoskun.tableview.listener.ITableViewListener; /** * Created by evrencoskun on 22.11.2017. */ public abstract class AbstractItemClickListener implements RecyclerView.OnItemTouchListener { private ITableViewListener mListener; @NonNull protected GestureDetector mGestureDetector; @NonNull protected CellRecyclerView mRecyclerView; @NonNull protected SelectionHandler mSelectionHandler; @NonNull protected ITableView mTableView; public AbstractItemClickListener(@NonNull CellRecyclerView recyclerView, @NonNull ITableView tableView) { this.mRecyclerView = recyclerView; this.mTableView = tableView; this.mSelectionHandler = tableView.getSelectionHandler(); mGestureDetector = new GestureDetector(mRecyclerView.getContext(), new GestureDetector .SimpleOnGestureListener() { @Nullable MotionEvent start; @Override public boolean onSingleTapUp(MotionEvent e) { return true; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { return clickAction(mRecyclerView, e); } @Override public boolean onDoubleTap(MotionEvent e) { return doubleClickAction(e); } @Override public boolean onDown(MotionEvent e) { start = e; return false; } @Override public void onLongPress(MotionEvent e) { // Check distance to prevent scroll to trigger the event if (start != null && Math.abs(start.getRawX() - e.getRawX()) < 20 && Math.abs (start.getRawY() - e.getRawY()) < 20) { longPressAction(e); } } }); } @Override public boolean onInterceptTouchEvent(@NonNull RecyclerView view, @NonNull MotionEvent e) { mGestureDetector.onTouchEvent(e); // Return false intentionally return false; } @Override public void onTouchEvent(@NonNull RecyclerView view, @NonNull MotionEvent motionEvent) { } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } @NonNull protected ITableViewListener getTableViewListener() { if (mListener == null) { mListener = mTableView.getTableViewListener(); } return mListener; } abstract protected boolean clickAction(@NonNull RecyclerView view, @NonNull MotionEvent e); abstract protected void longPressAction(@NonNull MotionEvent e); abstract protected boolean doubleClickAction(@NonNull MotionEvent e); } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/listener/itemclick/CellRecyclerViewItemClickListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.listener.itemclick; import android.view.MotionEvent; import android.view.View; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerView; import com.evrencoskun.tableview.adapter.recyclerview.CellRowRecyclerViewAdapter; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; /** * Created by evrencoskun on 26/09/2017. */ public class CellRecyclerViewItemClickListener extends AbstractItemClickListener { @NonNull private final CellRecyclerView mCellRecyclerView; public CellRecyclerViewItemClickListener(@NonNull CellRecyclerView recyclerView, @NonNull ITableView tableView) { super(recyclerView, tableView); this.mCellRecyclerView = tableView.getCellRecyclerView(); } @Override protected boolean clickAction(@NonNull RecyclerView view, @NonNull MotionEvent e) { // Get interacted view from x,y coordinate. View childView = view.findChildViewUnder(e.getX(), e.getY()); if (childView != null) { // Find the view holder AbstractViewHolder holder = (AbstractViewHolder) mRecyclerView.getChildViewHolder (childView); // Get y position from adapter CellRowRecyclerViewAdapter adapter = (CellRowRecyclerViewAdapter) mRecyclerView .getAdapter(); int column = holder.getBindingAdapterPosition(); int row = adapter.getYPosition(); // Control to ignore selection color if (!mTableView.isIgnoreSelectionColors()) { mSelectionHandler.setSelectedCellPositions(holder, column, row); } // Call ITableView listener for item click getTableViewListener().onCellClicked(holder, column, row); return true; } return false; } @Override protected void longPressAction(@NonNull MotionEvent e) { // Consume the action for the time when either the cell row recyclerView or // the cell recyclerView is scrolling. if ((mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) || (mCellRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) { return; } // Get interacted view from x,y coordinate. View child = mRecyclerView.findChildViewUnder(e.getX(), e.getY()); if (child != null) { // Find the view holder RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(child); // Get y position from adapter CellRowRecyclerViewAdapter adapter = (CellRowRecyclerViewAdapter) mRecyclerView .getAdapter(); // Call ITableView listener for long click getTableViewListener().onCellLongPressed(holder, holder.getBindingAdapterPosition(), adapter.getYPosition()); } } @Override protected boolean doubleClickAction(@NonNull MotionEvent e) { // Get interacted view from x,y coordinate. View childView = mRecyclerView.findChildViewUnder(e.getX(), e.getY()); if (childView != null) { // Find the view holder AbstractViewHolder holder = (AbstractViewHolder) mRecyclerView.getChildViewHolder (childView); // Get y position from adapter CellRowRecyclerViewAdapter adapter = (CellRowRecyclerViewAdapter) mRecyclerView .getAdapter(); int column = holder.getBindingAdapterPosition(); int row = adapter.getYPosition(); // Control to ignore selection color if (!mTableView.isIgnoreSelectionColors()) { mSelectionHandler.setSelectedCellPositions(holder, column, row); } // Call ITableView listener for item click getTableViewListener().onCellDoubleClicked(holder, column, row); return true; } return false; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/listener/itemclick/ColumnHeaderRecyclerViewItemClickListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.listener.itemclick; import android.view.MotionEvent; import android.view.View; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerView; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; /** * Created by evrencoskun on 26/09/2017. */ public class ColumnHeaderRecyclerViewItemClickListener extends AbstractItemClickListener { public ColumnHeaderRecyclerViewItemClickListener(@NonNull CellRecyclerView recyclerView, @NonNull ITableView tableView) { super(recyclerView, tableView); } @Override protected boolean clickAction(@NonNull RecyclerView view, @NonNull MotionEvent e) { // Get interacted view from x,y coordinate. View childView = view.findChildViewUnder(e.getX(), e.getY()); if (childView != null) { // Find the view holder AbstractViewHolder holder = (AbstractViewHolder) mRecyclerView.getChildViewHolder (childView); int column = holder.getBindingAdapterPosition(); // Control to ignore selection color if (!mTableView.isIgnoreSelectionColors()) { mSelectionHandler.setSelectedColumnPosition(holder, column); } // Call ITableView listener for item click getTableViewListener().onColumnHeaderClicked(holder, column); return true; } return false; } protected void longPressAction(@NonNull MotionEvent e) { // Consume the action for the time when the recyclerView is scrolling. if (mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) { return; } // Get interacted view from x,y coordinate. View child = mRecyclerView.findChildViewUnder(e.getX(), e.getY()); if (child != null) { // Find the view holder RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(child); // Call ITableView listener for long click getTableViewListener().onColumnHeaderLongPressed(holder, holder.getBindingAdapterPosition()); } } @Override protected boolean doubleClickAction(@NonNull MotionEvent e) { // Get interacted view from x,y coordinate. View childView = mRecyclerView.findChildViewUnder(e.getX(), e.getY()); if (childView != null) { // Find the view holder AbstractViewHolder holder = (AbstractViewHolder) mRecyclerView.getChildViewHolder (childView); int column = holder.getBindingAdapterPosition(); // Control to ignore selection color if (!mTableView.isIgnoreSelectionColors()) { mSelectionHandler.setSelectedColumnPosition(holder, column); } // Call ITableView listener for item click getTableViewListener().onColumnHeaderDoubleClicked(holder, column); return true; } return false; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/listener/itemclick/RowHeaderRecyclerViewItemClickListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.listener.itemclick; import android.view.MotionEvent; import android.view.View; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerView; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; /** * Created by evrencoskun on 26/09/2017. */ public class RowHeaderRecyclerViewItemClickListener extends AbstractItemClickListener { public RowHeaderRecyclerViewItemClickListener(@NonNull CellRecyclerView recyclerView, @NonNull ITableView tableView) { super(recyclerView, tableView); } @Override protected boolean clickAction(@NonNull RecyclerView view, @NonNull MotionEvent e) { // Get interacted view from x,y coordinate. View childView = view.findChildViewUnder(e.getX(), e.getY()); if (childView != null) { // Find the view holder AbstractViewHolder holder = (AbstractViewHolder) mRecyclerView.getChildViewHolder (childView); int row = holder.getBindingAdapterPosition(); // Control to ignore selection color if (!mTableView.isIgnoreSelectionColors()) { mSelectionHandler.setSelectedRowPosition(holder, row); } // Call ITableView listener for item click getTableViewListener().onRowHeaderClicked(holder, row); return true; } return false; } protected void longPressAction(@NonNull MotionEvent e) { // Consume the action for the time when the recyclerView is scrolling. if (mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) { return; } // Get interacted view from x,y coordinate. View child = mRecyclerView.findChildViewUnder(e.getX(), e.getY()); if (child != null) { // Find the view holder RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(child); // Call ITableView listener for long click getTableViewListener().onRowHeaderLongPressed(holder, holder.getBindingAdapterPosition()); } } @Override protected boolean doubleClickAction(@NonNull MotionEvent e) { // Get interacted view from x,y coordinate. View childView = mRecyclerView.findChildViewUnder(e.getX(), e.getY()); if (childView != null) { // Find the view holder AbstractViewHolder holder = (AbstractViewHolder) mRecyclerView.getChildViewHolder (childView); int row = holder.getBindingAdapterPosition(); // Control to ignore selection color if (!mTableView.isIgnoreSelectionColors()) { mSelectionHandler.setSelectedRowPosition(holder, row); } // Call ITableView listener for item click getTableViewListener().onRowHeaderDoubleClicked(holder, row); return true; } return false; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/listener/scroll/HorizontalRecyclerViewListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.listener.scroll; import android.util.Log; import android.view.MotionEvent; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerView; /** * Created by evrencoskun on 19/06/2017. */ public class HorizontalRecyclerViewListener extends RecyclerView.OnScrollListener implements RecyclerView.OnItemTouchListener { private static final String LOG_TAG = HorizontalRecyclerViewListener.class.getSimpleName(); @NonNull private final CellRecyclerView mColumnHeaderRecyclerView; @Nullable private final RecyclerView.LayoutManager mCellLayoutManager; @Nullable private RecyclerView mLastTouchedRecyclerView; // X position means column position private int mXPosition; private boolean mIsMoved; private int mScrollPosition; private int mScrollPositionOffset = 0; @Nullable private RecyclerView mCurrentRVTouched = null; @NonNull private final VerticalRecyclerViewListener mVerticalRecyclerViewListener; public HorizontalRecyclerViewListener(@NonNull ITableView tableView) { this.mColumnHeaderRecyclerView = tableView.getColumnHeaderRecyclerView(); this.mCellLayoutManager = tableView.getCellRecyclerView().getLayoutManager(); this.mVerticalRecyclerViewListener = tableView.getVerticalRecyclerViewListener(); } @Override public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { // Prevent multitouch, once we start to listen with a RV, // we ignore any other RV until the touch is released (UP) if (mCurrentRVTouched != null && rv != mCurrentRVTouched) { return true; } if (e.getAction() == MotionEvent.ACTION_DOWN) { mCurrentRVTouched = rv; if (rv.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) { if (mLastTouchedRecyclerView != null && rv != mLastTouchedRecyclerView) { if (mLastTouchedRecyclerView == mColumnHeaderRecyclerView) { mColumnHeaderRecyclerView.removeOnScrollListener(this); mColumnHeaderRecyclerView.stopScroll(); Log.d(LOG_TAG, "Scroll listener has been removed to " + "mColumnHeaderRecyclerView at last touch control"); } else { int lastTouchedIndex = getIndex(mLastTouchedRecyclerView); // Control whether the last touched recyclerView is still attached or not if (lastTouchedIndex >= 0 && lastTouchedIndex < mCellLayoutManager .getChildCount()) { // Control the scroll listener is already removed. For instance; if user // scroll the parent recyclerView vertically, at that time, // ACTION_CANCEL // will be triggered that removes the scroll listener of the last // touched // recyclerView. if (!((CellRecyclerView) mLastTouchedRecyclerView) .isHorizontalScrollListenerRemoved()) { // Remove scroll listener of the last touched recyclerView // Because user touched another recyclerView before the last one get // SCROLL_STATE_IDLE state that removes the scroll listener ((RecyclerView) mCellLayoutManager.getChildAt(lastTouchedIndex)) .removeOnScrollListener(this); Log.d(LOG_TAG, "Scroll listener has been removed to " + mLastTouchedRecyclerView.getId() + " CellRecyclerView " + "at last touch control"); // the last one scroll must be stopped to be sync with others ((RecyclerView) mCellLayoutManager.getChildAt(lastTouchedIndex)) .stopScroll(); } } } } mXPosition = ((CellRecyclerView) rv).getScrolledX(); rv.addOnScrollListener(this); Log.d(LOG_TAG, "Scroll listener has been added to " + rv.getId() + " at action " + "down"); } } else if (e.getAction() == MotionEvent.ACTION_MOVE) { mCurrentRVTouched = rv; // Why does it matter ? // user scroll any recyclerView like brushing, at that time, ACTION_UP will be // triggered // before scrolling. So, we need to store whether it moved or not. mIsMoved = true; } else if (e.getAction() == MotionEvent.ACTION_UP) { mCurrentRVTouched = null; int nScrollX = ((CellRecyclerView) rv).getScrolledX(); // Is it just touched without scrolling then remove the listener if (mXPosition == nScrollX && !mIsMoved) { rv.removeOnScrollListener(this); Log.d(LOG_TAG, "Scroll listener has been removed to " + rv.getId() + " at " + "action" + " up"); } mLastTouchedRecyclerView = rv; } else if (e.getAction() == MotionEvent.ACTION_CANCEL) { // ACTION_CANCEL action will be triggered if users try to scroll vertically // For this situation, it doesn't matter whether the x position is changed or not // Beside this, even if moved action will be triggered, scroll listener won't // triggered on cancel action. So, we need to change state of the mIsMoved value as // well. // Renew the scroll position and its offset renewScrollPosition(rv); rv.removeOnScrollListener(this); Log.d(LOG_TAG, "Scroll listener has been removed to " + rv.getId() + " at action " + "cancel"); mIsMoved = false; mLastTouchedRecyclerView = rv; mCurrentRVTouched = null; } return false; } @Override public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { // Column Header should be scrolled firstly. Because it is the compared recyclerView to // make column width fit. if (recyclerView == mColumnHeaderRecyclerView) { super.onScrolled(recyclerView, dx, dy); // Scroll each cell recyclerViews for (int i = 0; i < mCellLayoutManager.getChildCount(); i++) { CellRecyclerView child = (CellRecyclerView) mCellLayoutManager.getChildAt(i); // Scroll horizontally child.scrollBy(dx, 0); } } else { // Scroll column header recycler view as well //mColumnHeaderRecyclerView.scrollBy(dx, 0); super.onScrolled(recyclerView, dx, dy); // Scroll each cell recyclerViews except the current touched one for (int i = 0; i < mCellLayoutManager.getChildCount(); i++) { CellRecyclerView child = (CellRecyclerView) mCellLayoutManager.getChildAt(i); if (child != recyclerView) { // Scroll horizontally child.scrollBy(dx, 0); } } } } @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE) { // Renew the scroll position and its offset renewScrollPosition(recyclerView); recyclerView.removeOnScrollListener(this); Log.d(LOG_TAG, "Scroll listener has been removed to " + recyclerView.getId() + " at " + "onScrollStateChanged"); mIsMoved = false; // When a user scrolls horizontally, VerticalRecyclerView add vertical scroll // listener because of touching process.However, mVerticalRecyclerViewListener // doesn't know anything about it. So, it is necessary to remove the last touched // recyclerView which uses the mVerticalRecyclerViewListener. boolean isNeeded = mLastTouchedRecyclerView != mColumnHeaderRecyclerView; mVerticalRecyclerViewListener.removeLastTouchedRecyclerViewScrollListener(isNeeded); } } private int getIndex(@NonNull RecyclerView rv) { for (int i = 0; i < mCellLayoutManager.getChildCount(); i++) { if (mCellLayoutManager.getChildAt(i) == rv) { return i; } } return -1; } /** * This method calculates the current scroll position and its offset to help new attached * recyclerView on window at that position and offset * * @see #getScrollPosition() * @see #getScrollPositionOffset() */ private void renewScrollPosition(@NonNull RecyclerView recyclerView) { LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); mScrollPosition = layoutManager.findFirstCompletelyVisibleItemPosition(); // That means there is no completely visible Position. if (mScrollPosition == -1) { mScrollPosition = layoutManager.findFirstVisibleItemPosition(); // That means there is just a visible item on the screen if (mScrollPosition == layoutManager.findLastVisibleItemPosition()) { // in this case we use the position which is the last & first visible item. } else { // That means there are 2 visible item on the screen. However, second one is not // completely visible. mScrollPosition = mScrollPosition + 1; } } mScrollPositionOffset = layoutManager.findViewByPosition(mScrollPosition).getLeft(); } /** * When parent RecyclerView scrolls vertically, the child horizontal recycler views should be * displayed on right scroll position. So the first complete visible position of the * recyclerView is stored as a member to use it for a new attached recyclerview whose * orientation is horizontal as well. * * @see #getScrollPositionOffset() */ public int getScrollPosition() { return mScrollPosition; } /** * Users can scroll the recyclerViews to the any x position which may not the exact position. So * we need to know store the offset value to locate a specific location for a new attached * recyclerView * * @see #getScrollPosition() */ public int getScrollPositionOffset() { return mScrollPositionOffset; } public void setScrollPositionOffset(int offset) { mScrollPositionOffset = offset; } /** * To change default scroll position that is before TableView is not populated. */ public void setScrollPosition(int position) { this.mScrollPosition = position; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/listener/scroll/VerticalRecyclerViewListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.listener.scroll; import android.util.Log; import android.view.MotionEvent; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerView; /** * Created by evrencoskun on 30/06/2017. */ public class VerticalRecyclerViewListener extends RecyclerView.OnScrollListener implements RecyclerView.OnItemTouchListener { private static final String LOG_TAG = VerticalRecyclerViewListener.class.getSimpleName(); @NonNull private final CellRecyclerView mRowHeaderRecyclerView, mCellRecyclerView; private RecyclerView mLastTouchedRecyclerView; // Y Position means row position private int mYPosition; private boolean mIsMoved; @Nullable private RecyclerView mCurrentRVTouched = null; public VerticalRecyclerViewListener(@NonNull ITableView tableView) { this.mRowHeaderRecyclerView = tableView.getRowHeaderRecyclerView(); this.mCellRecyclerView = tableView.getCellRecyclerView(); } private float dx = 0, dy = 0; /** * check which direction the user is scrolling * * @param ev * @return */ private boolean verticalDirection(@NonNull MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_MOVE) { if (dx == 0) { dx = ev.getX(); } if (dy == 0) { dy = ev.getY(); } float xdiff = Math.abs(dx - ev.getX()); float ydiff = Math.abs(dy - ev.getY()); dx = ev.getX(); dy = ev.getY(); // if user scrolled more horizontally than vertically return xdiff <= ydiff; } return true; } @Override public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { // Prevent multitouch, once we start to listen with a RV, // we ignore any other RV until the touch is released (UP) if ((mCurrentRVTouched != null && rv != mCurrentRVTouched)) { return true; } // If scroll direction is not Vertical, then ignore and reset last RV touched if (!verticalDirection(e)) { mCurrentRVTouched = null; return false; } if (e.getAction() == MotionEvent.ACTION_DOWN) { mCurrentRVTouched = rv; if (rv.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) { if (mLastTouchedRecyclerView != null && rv != mLastTouchedRecyclerView) { removeLastTouchedRecyclerViewScrollListener(false); } mYPosition = ((CellRecyclerView) rv).getScrolledY(); rv.addOnScrollListener(this); if (rv == mCellRecyclerView) { Log.d(LOG_TAG, "mCellRecyclerView scroll listener added"); } else if (rv == mRowHeaderRecyclerView) { Log.d(LOG_TAG, "mRowHeaderRecyclerView scroll listener added"); } // Refresh the value; mIsMoved = false; } } else if (e.getAction() == MotionEvent.ACTION_MOVE) { mCurrentRVTouched = rv; // Why does it matter ? // user scroll any recyclerView like brushing, at that time, ACTION_UP will be // triggered // before scrolling. So, we need to store whether it moved or not. mIsMoved = true; } else if (e.getAction() == MotionEvent.ACTION_UP) { mCurrentRVTouched = null; int nScrollY = ((CellRecyclerView) rv).getScrolledY(); // TODO: Even if moved value is true and it may not scroll. This should be fixed. // TODO: The scenario is scroll lightly center RecyclerView vertically. // TODO: Below if condition may be changed later. // Is it just touched without scrolling then remove the listener if (mYPosition == nScrollY && !mIsMoved && rv.getScrollState() == RecyclerView .SCROLL_STATE_IDLE) { rv.removeOnScrollListener(this); if (rv == mCellRecyclerView) { Log.d(LOG_TAG, "mCellRecyclerView scroll listener removed from up "); } else if (rv == mRowHeaderRecyclerView) { Log.d(LOG_TAG, "mRowHeaderRecyclerView scroll listener removed from up"); } } mLastTouchedRecyclerView = rv; } return false; } @Override public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { // CellRecyclerViews should be scrolled after the RowHeaderRecyclerView. // Because it is one of the main compared criterion to make each columns fit. if (recyclerView == mCellRecyclerView) { super.onScrolled(recyclerView, dx, dy); // The below code has been moved in CellLayoutManager //mRowHeaderRecyclerView.scrollBy(0, dy); } else if (recyclerView == mRowHeaderRecyclerView) { super.onScrolled(recyclerView, dx, dy); mCellRecyclerView.scrollBy(0, dy); } } @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE) { recyclerView.removeOnScrollListener(this); mIsMoved = false; mCurrentRVTouched = null; if (recyclerView == mCellRecyclerView) { Log.d(LOG_TAG, "mCellRecyclerView scroll listener removed from " + "onScrollStateChanged"); } else if (recyclerView == mRowHeaderRecyclerView) { Log.d(LOG_TAG, "mRowHeaderRecyclerView scroll listener removed from " + "onScrollStateChanged"); } } } /** * If current recyclerView that is touched to scroll is not same as the last one, this method * helps to remove the scroll listener of the last touched recyclerView. * This method is a little bit different from HorizontalRecyclerViewListener. * * @param isNeeded Is mCellRecyclerView scroll listener should be removed ? The scenario is a * user scrolls vertically using RowHeaderRecyclerView. After that, the user * scrolls horizontally using ColumnHeaderRecyclerView. */ public void removeLastTouchedRecyclerViewScrollListener(boolean isNeeded) { if (mLastTouchedRecyclerView == mCellRecyclerView) { mCellRecyclerView.removeOnScrollListener(this); mCellRecyclerView.stopScroll(); Log.d(LOG_TAG, "mCellRecyclerView scroll listener removed from last touched"); } else { mRowHeaderRecyclerView.removeOnScrollListener(this); mRowHeaderRecyclerView.stopScroll(); Log.d(LOG_TAG, "mRowHeaderRecyclerView scroll listener removed from last touched"); if (isNeeded) { mCellRecyclerView.removeOnScrollListener(this); mCellRecyclerView.stopScroll(); Log.d(LOG_TAG, "mCellRecyclerView scroll listener removed from last touched"); } } } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/pagination/IPagination.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.pagination; import androidx.annotation.Nullable; public interface IPagination { /** * Loads the next page of the data set to the table view. */ void nextPage(); /** * Loads the previous page of the data set to the table view. */ void previousPage(); /** * Loads the data set of the specified page to the table view. * * @param page The page to be loaded. */ void goToPage(int page); /** * Sets the number of items (rows) per page to be displayed in the table view. * * @param numItems The number of items per page. */ void setItemsPerPage(int numItems); /** * Sets the OnTableViewPageTurnedListener for this Pagination. * * @param onTableViewPageTurnedListener The OnTableViewPageTurnedListener. */ void setOnTableViewPageTurnedListener(@Nullable Pagination.OnTableViewPageTurnedListener onTableViewPageTurnedListener); /** * Removes the OnTableViewPageTurnedListener for this Pagination. */ void removeOnTableViewPageTurnedListener(); /** * @return The current page loaded to the table view. */ int getCurrentPage(); /** * @return The number of items per page loaded to the table view. */ int getItemsPerPage(); /** * @return The number of pages in the pagination. */ int getPageCount(); /** * @return Current pagination state of the table view. */ boolean isPaginated(); } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/pagination/Pagination.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.pagination; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.evrencoskun.tableview.ITableView; import com.evrencoskun.tableview.adapter.AdapterDataSetChangedListener; import com.evrencoskun.tableview.adapter.recyclerview.CellRecyclerViewAdapter; import com.evrencoskun.tableview.adapter.recyclerview.RowHeaderRecyclerViewAdapter; import com.evrencoskun.tableview.filter.FilterChangedListener; import com.evrencoskun.tableview.sort.ColumnForRowHeaderSortComparator; import com.evrencoskun.tableview.sort.ColumnSortComparator; import com.evrencoskun.tableview.sort.ColumnSortStateChangedListener; import com.evrencoskun.tableview.sort.ISortableModel; import com.evrencoskun.tableview.sort.RowHeaderForCellSortComparator; import com.evrencoskun.tableview.sort.RowHeaderSortComparator; import com.evrencoskun.tableview.sort.SortState; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Pagination implements IPagination { private static final int DEFAULT_ITEMS_PER_PAGE = 10; private int itemsPerPage; private int currentPage; private int pageCount; @NonNull private List> originalCellData; @NonNull private List originalRowData; @Nullable private RowHeaderRecyclerViewAdapter mRowHeaderRecyclerViewAdapter; @Nullable private CellRecyclerViewAdapter> mCellRecyclerViewAdapter; @Nullable private OnTableViewPageTurnedListener onTableViewPageTurnedListener; /** * Basic constructor, TableView instance is required. * * @param tableView The TableView to be paginated. */ public Pagination(@NonNull ITableView tableView) { this(tableView, DEFAULT_ITEMS_PER_PAGE, null); } /** * Applies pagination to the supplied TableView with number of items per page. * * @param tableView The TableView to be paginated. * @param itemsPerPage The number of items per page. */ public Pagination(@NonNull ITableView tableView, int itemsPerPage) { this(tableView, itemsPerPage, null); } /** * Applies pagination to the supplied TableView with number of items per page and an * OnTableViewPageTurnedListener for handling changes in the TableView pagination. * * @param tableView The TableView to be paginated. * @param itemsPerPage The number of items per page. * @param listener The OnTableViewPageTurnedListener for the TableView. */ public Pagination(@NonNull ITableView tableView, int itemsPerPage, @Nullable OnTableViewPageTurnedListener listener) { initialize(tableView, itemsPerPage, listener); } @SuppressWarnings("unchecked") private void initialize(@NonNull ITableView tableView, int itemsPerPage, @Nullable OnTableViewPageTurnedListener listener) { this.onTableViewPageTurnedListener = listener; this.itemsPerPage = itemsPerPage; this.mRowHeaderRecyclerViewAdapter = (RowHeaderRecyclerViewAdapter) tableView .getRowHeaderRecyclerView().getAdapter(); this.mCellRecyclerViewAdapter = (CellRecyclerViewAdapter) tableView.getCellRecyclerView() .getAdapter(); tableView.getColumnSortHandler().addColumnSortStateChangedListener(columnSortStateChangedListener); tableView.getAdapter().addAdapterDataSetChangedListener(adapterDataSetChangedListener); tableView.getFilterHandler().addFilterChangedListener(filterChangedListener); this.originalCellData = tableView.getAdapter().getCellRecyclerViewAdapter().getItems(); this.originalRowData = tableView.getAdapter().getRowHeaderRecyclerViewAdapter().getItems(); this.currentPage = 1; reloadPages(); } private void reloadPages() { paginateData(); goToPage(currentPage); } private void paginateData() { int start, end; List> currentPageCellData = new ArrayList<>(); List currentPageRowData = new ArrayList<>(); // No pagination if itemsPerPage is 0, all data will be loaded into the TableView. if (itemsPerPage == 0) { currentPageCellData.addAll(originalCellData); currentPageRowData.addAll(originalRowData); pageCount = 1; start = 0; end = currentPageCellData.size(); } else { start = (currentPage * itemsPerPage) - itemsPerPage; end = (currentPage * itemsPerPage) > originalCellData.size() ? originalCellData.size() : (currentPage * itemsPerPage); for (int x = start; x < end; x++) { currentPageCellData.add(originalCellData.get(x)); currentPageRowData.add(originalRowData.get(x)); } // Using ceiling to calculate number of pages, e.g. 103 items of 10 items per page // will result to 11 pages. pageCount = (int) Math.ceil((double) originalCellData.size() / itemsPerPage); } // Sets the paginated data to the TableView. mRowHeaderRecyclerViewAdapter.setItems(currentPageRowData, true); mCellRecyclerViewAdapter.setItems(currentPageCellData, true); // Dispatches TableView changes to Listener interface if (onTableViewPageTurnedListener != null) { onTableViewPageTurnedListener.onPageTurned(currentPageCellData.size(), start, end - 1); } } @Override public void nextPage() { currentPage = currentPage + 1 > pageCount ? currentPage : ++currentPage; paginateData(); } @Override public void previousPage() { currentPage = currentPage - 1 == 0 ? currentPage : --currentPage; paginateData(); } @Override public void goToPage(int page) { currentPage = (page > pageCount || page < 1) ? (page > pageCount && pageCount > 0 ? pageCount : currentPage) : page; paginateData(); } @Override public void setItemsPerPage(int numItems) { itemsPerPage = numItems; currentPage = 1; paginateData(); } @Override public void setOnTableViewPageTurnedListener(@Nullable OnTableViewPageTurnedListener onTableViewPageTurnedListener) { this.onTableViewPageTurnedListener = onTableViewPageTurnedListener; } @Override public void removeOnTableViewPageTurnedListener() { this.onTableViewPageTurnedListener = null; } @Override public int getCurrentPage() { return currentPage; } @Override public int getItemsPerPage() { return itemsPerPage; } @Override public int getPageCount() { return pageCount; } @Override public boolean isPaginated() { return itemsPerPage > 0; } @NonNull @SuppressWarnings("unchecked") private final AdapterDataSetChangedListener adapterDataSetChangedListener = new AdapterDataSetChangedListener() { @Override public void onRowHeaderItemsChanged(@NonNull List rowHeaderItems) { originalRowData = new ArrayList<>(rowHeaderItems); reloadPages(); } @Override public void onCellItemsChanged(@NonNull List cellItems) { originalCellData = new ArrayList<>(cellItems); reloadPages(); } }; @NonNull private final FilterChangedListener filterChangedListener = new FilterChangedListener() { @Override public void onFilterChanged(@NonNull List> filteredCellItems, @NonNull List filteredRowHeaderItems) { originalCellData = new ArrayList<>(filteredCellItems); originalRowData = new ArrayList<>(filteredRowHeaderItems); reloadPages(); } @Override public void onFilterCleared(@NonNull List> originalCellItems, @NonNull List originalRowHeaderItems) { originalCellData = new ArrayList<>(originalCellItems); originalRowData = new ArrayList<>(originalRowHeaderItems); reloadPages(); } }; @NonNull private final ColumnSortStateChangedListener columnSortStateChangedListener = new ColumnSortStateChangedListener() { @Override public void onColumnSortStatusChanged(int column, @NonNull SortState sortState) { paginateOnColumnSort(column, sortState); } @Override public void onRowHeaderSortStatusChanged(@NonNull SortState sortState) { paginateOnColumnSort(-1, sortState); } }; private void paginateOnColumnSort(int column, @NonNull SortState sortState) { List sortedRowHeaderList = new ArrayList<>(originalRowData); List> sortedList = new ArrayList<>(originalCellData); if (sortState != SortState.UNSORTED) { if (column == -1) { Collections.sort(sortedRowHeaderList, new RowHeaderSortComparator(sortState)); RowHeaderForCellSortComparator rowHeaderForCellSortComparator = new RowHeaderForCellSortComparator( originalRowData, originalCellData, sortState ); Collections.sort(sortedList, rowHeaderForCellSortComparator); } else { Collections.sort(sortedList, new ColumnSortComparator(column, sortState)); ColumnForRowHeaderSortComparator columnForRowHeaderSortComparator = new ColumnForRowHeaderSortComparator( originalRowData, originalCellData, column, sortState ); Collections.sort(sortedRowHeaderList, columnForRowHeaderSortComparator); } } originalRowData = new ArrayList<>(sortedRowHeaderList); originalCellData = new ArrayList<>(sortedList); reloadPages(); } /** * Listener interface for changing of TableView page. */ public interface OnTableViewPageTurnedListener { /** * Called when the page is changed in the TableView. * * @param numItems The number of items currently being displayed in the TableView. * @param itemsStart The starting item currently being displayed in the TableView. * @param itemsEnd The ending item currently being displayed in the TableView. */ void onPageTurned(int numItems, int itemsStart, int itemsEnd); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/preference/Preferences.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.preference; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; /** * Created by evrencoskun on 4.03.2018. */ public class Preferences implements Parcelable { public int rowPosition; public int rowPositionOffset; public int columnPosition; public int columnPositionOffset; public int selectedRowPosition; public int selectedColumnPosition; public Preferences() { } protected Preferences(Parcel in) { rowPosition = in.readInt(); rowPositionOffset = in.readInt(); columnPosition = in.readInt(); columnPositionOffset = in.readInt(); selectedRowPosition = in.readInt(); selectedColumnPosition = in.readInt(); } @NonNull public static final Creator CREATOR = new Creator() { @NonNull @Override public Preferences createFromParcel(Parcel in) { return new Preferences(in); } @NonNull @Override public Preferences[] newArray(int size) { return new Preferences[size]; } }; /** * Describe the kinds of special objects contained in this Parcelable * instance's marshaled representation. For example, if the object will * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)}, * the return value of this method must include the * {@link #CONTENTS_FILE_DESCRIPTOR} bit. * * @return a bitmask indicating the set of special object types marshaled by this Parcelable * object instance. */ @Override public int describeContents() { return 0; } /** * Flatten this object in to a Parcel. * * @param dest The Parcel in which the object should be written. * @param flags Additional flags about how the object should be written. May be 0 or {@link * #PARCELABLE_WRITE_RETURN_VALUE}. */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(rowPosition); dest.writeInt(rowPositionOffset); dest.writeInt(columnPosition); dest.writeInt(columnPositionOffset); dest.writeInt(selectedRowPosition); dest.writeInt(selectedColumnPosition); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/preference/SavedState.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.preference; import android.os.Parcel; import android.os.Parcelable; import android.view.View; import androidx.annotation.NonNull; /** * Created by evrencoskun on 4.03.2018. */ public class SavedState extends View.BaseSavedState { public Preferences preferences; public SavedState(Parcelable superState) { super(superState); } private SavedState(Parcel in) { super(in); preferences = in.readParcelable(Preferences.class.getClassLoader()); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeParcelable(preferences, flags); } @NonNull public static final Parcelable.Creator CREATOR = new Parcelable .Creator() { @NonNull public SavedState createFromParcel(Parcel in) { return new SavedState(in); } @NonNull public SavedState[] newArray(int size) { return new SavedState[size]; } }; } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/sort/AbstractSortComparator.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.sort; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.util.Date; /** * Created by cedricferry on 6/2/18. */ public abstract class AbstractSortComparator { @NonNull protected SortState mSortState; protected int compareContent(@Nullable Object o1, @Nullable Object o2) { if (o1 == null && o2 == null) { return 0; } else if (o1 == null) { return -1; } else if (o2 == null) { return 1; } else { Class type = o1.getClass(); if (Comparable.class.isAssignableFrom(type)) { return ((Comparable) o1).compareTo(o2); } else if (type.getSuperclass() == Number.class) { return compare((Number) o1, (Number) o2); } else if (type == String.class) { return ((String) o1).compareTo((String) o2); } else if (type == Date.class) { return compare((Date) o1, (Date) o2); } else if (type == Boolean.class) { return compare((Boolean) o1, (Boolean) o2); } else { return ((String) o1).compareTo((String) o2); } } } public int compare(Number o1, Number o2) { double n1 = o1.doubleValue(); double n2 = o2.doubleValue(); return Double.compare(n1, n2); } public int compare(Date o1, Date o2) { long n1 = o1.getTime(); long n2 = o2.getTime(); return Long.compare(n1, n2); } public int compare(Boolean o1, Boolean o2) { return Boolean.compare(o1, o2); } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/sort/ColumnForRowHeaderSortComparator.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.sort; import androidx.annotation.NonNull; import java.util.Comparator; import java.util.List; /** * In order to keep RowHeader DataSet and Main DataSet aligned * it is required to sort RowHeader the same. * So if MainDataSet row 1 moved to position 10, RowHeader 1 move to position 10 too. * To accomplish that we need to set a comparator that use MainDataSet * in order to sort RowHeader. * Created by cedricferry on 7/2/18. */ public class ColumnForRowHeaderSortComparator implements Comparator { @NonNull private final List mRowHeaderList; @NonNull private final List> mReferenceList; private final int column; @NonNull private final SortState mSortState; @NonNull private final ColumnSortComparator mColumnSortComparator; public ColumnForRowHeaderSortComparator(@NonNull List rowHeader, @NonNull List> referenceList, int column, @NonNull SortState sortState) { this.mRowHeaderList = rowHeader; this.mReferenceList = referenceList; this.column = column; this.mSortState = sortState; this.mColumnSortComparator = new ColumnSortComparator(column, sortState); } @Override public int compare(ISortableModel o, ISortableModel t1) { Object o1 = mReferenceList.get(this.mRowHeaderList.indexOf(o)).get(column).getContent(); Object o2 = mReferenceList.get(this.mRowHeaderList.indexOf(t1)).get(column).getContent(); if (mSortState == SortState.DESCENDING) { return mColumnSortComparator.compareContent(o2, o1); } else { return mColumnSortComparator.compareContent(o1, o2); } } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/sort/ColumnSortCallback.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.sort; import androidx.annotation.NonNull; import androidx.core.util.ObjectsCompat; import androidx.recyclerview.widget.DiffUtil; import java.util.List; /** * Created by evrencoskun on 23.11.2017. */ public class ColumnSortCallback extends DiffUtil.Callback { @NonNull private final List> mOldCellItems; @NonNull private final List> mNewCellItems; private final int mColumnPosition; public ColumnSortCallback(@NonNull List> oldCellItems, @NonNull List> newCellItems, int column) { this.mOldCellItems = oldCellItems; this.mNewCellItems = newCellItems; this.mColumnPosition = column; } @Override public int getOldListSize() { return mOldCellItems.size(); } @Override public int getNewListSize() { return mNewCellItems.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { // Control for precaution from IndexOutOfBoundsException if (mOldCellItems.size() > oldItemPosition && mNewCellItems.size() > newItemPosition) { if (mOldCellItems.get(oldItemPosition).size() > mColumnPosition && mNewCellItems.get (newItemPosition).size() > mColumnPosition) { // Compare ids String oldId = mOldCellItems.get(oldItemPosition).get(mColumnPosition).getId(); String newId = mNewCellItems.get(newItemPosition).get(mColumnPosition).getId(); return oldId.equals(newId); } } return false; } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { // Control for precaution from IndexOutOfBoundsException if (mOldCellItems.size() > oldItemPosition && mNewCellItems.size() > newItemPosition) { if (mOldCellItems.get(oldItemPosition).size() > mColumnPosition && mNewCellItems.get (newItemPosition).size() > mColumnPosition) { // Compare contents Object oldContent = mOldCellItems.get(oldItemPosition).get(mColumnPosition) .getContent(); Object newContent = mNewCellItems.get(newItemPosition).get(mColumnPosition) .getContent(); return ObjectsCompat.equals(oldContent, newContent); } } return false; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/sort/ColumnSortComparator.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.sort; import androidx.annotation.NonNull; import java.util.Comparator; import java.util.List; /** * Created by evrencoskun on 25.11.2017. */ public class ColumnSortComparator extends AbstractSortComparator implements Comparator> { private final int mXPosition; public ColumnSortComparator(int xPosition, @NonNull SortState sortState) { this.mXPosition = xPosition; this.mSortState = sortState; } @Override public int compare(List t1, List t2) { Object o1 = t1.get(mXPosition).getContent(); Object o2 = t2.get(mXPosition).getContent(); if (mSortState == SortState.DESCENDING) { return compareContent(o2, o1); } else { // Default sorting process is ASCENDING return compareContent(o1, o2); } } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/sort/ColumnSortHelper.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.sort; import androidx.annotation.NonNull; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractSorterViewHolder; import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder; import com.evrencoskun.tableview.layoutmanager.ColumnHeaderLayoutManager; import java.util.ArrayList; import java.util.List; /** * Created by evrencoskun on 15.12.2017. */ public class ColumnSortHelper { @NonNull private final List mSortingColumns = new ArrayList<>(); @NonNull private final ColumnHeaderLayoutManager mColumnHeaderLayoutManager; public ColumnSortHelper(@NonNull ColumnHeaderLayoutManager columnHeaderLayoutManager) { this.mColumnHeaderLayoutManager = columnHeaderLayoutManager; } private void sortingStatusChanged(int column, @NonNull SortState sortState) { AbstractViewHolder holder = mColumnHeaderLayoutManager.getViewHolder(column); if (holder != null) { if (holder instanceof AbstractSorterViewHolder) { ((AbstractSorterViewHolder) holder).onSortingStatusChanged(sortState); } else { throw new IllegalArgumentException("Column Header ViewHolder must extend " + "AbstractSorterViewHolder"); } } } public void setSortingStatus(int column, @NonNull SortState status) { Directive directive = getDirective(column); if (directive != EMPTY_DIRECTIVE) { mSortingColumns.remove(directive); } if (status != SortState.UNSORTED) { mSortingColumns.add(new Directive(column, status)); } sortingStatusChanged(column, status); } public void clearSortingStatus() { mSortingColumns.clear(); } public boolean isSorting() { return mSortingColumns.size() != 0; } @NonNull public SortState getSortingStatus(int column) { return getDirective(column).direction; } @NonNull private Directive getDirective(int column) { for (int i = 0; i < mSortingColumns.size(); i++) { Directive directive = mSortingColumns.get(i); if (directive.column == column) { return directive; } } return EMPTY_DIRECTIVE; } private static class Directive { private final int column; @NonNull private final SortState direction; Directive(int column, @NonNull SortState direction) { this.column = column; this.direction = direction; } } @NonNull private static final Directive EMPTY_DIRECTIVE = new Directive(-1, SortState.UNSORTED); } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/sort/ColumnSortStateChangedListener.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.sort; import androidx.annotation.NonNull; public abstract class ColumnSortStateChangedListener { /** * Dispatches sorting changes on a column to listeners. * * @param column Column to be sorted. * @param sortState SortState of the column to be sorted. */ public void onColumnSortStatusChanged(int column, @NonNull SortState sortState) { } /** * Dispatches sorting changes to the row header column to listeners. * * @param sortState SortState of the row header column. */ public void onRowHeaderSortStatusChanged(@NonNull SortState sortState) { } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/sort/ISortableModel.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.sort; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * Created by evrencoskun on 24.11.2017. */ public interface ISortableModel { @NonNull String getId(); @Nullable Object getContent(); } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/sort/RowHeaderForCellSortComparator.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.sort; import androidx.annotation.NonNull; import java.util.Comparator; import java.util.List; /** * Created by cedricferry on 14/2/18. */ public class RowHeaderForCellSortComparator implements Comparator> { @NonNull private final List mReferenceList; @NonNull private final List> mColumnList; @NonNull private final SortState mSortState; @NonNull private final RowHeaderSortComparator mRowHeaderSortComparator; public RowHeaderForCellSortComparator(@NonNull List referenceList, @NonNull List> columnList, @NonNull SortState sortState) { this.mReferenceList = referenceList; this.mColumnList = columnList; this.mSortState = sortState; this.mRowHeaderSortComparator = new RowHeaderSortComparator(sortState); } @Override public int compare(List o, List t1) { Object o1 = mReferenceList.get(this.mColumnList.indexOf(o)).getContent(); Object o2 = mReferenceList.get(this.mColumnList.indexOf(t1)).getContent(); if (mSortState == SortState.DESCENDING) { return mRowHeaderSortComparator.compareContent(o2, o1); } else { return mRowHeaderSortComparator.compareContent(o1, o2); } } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/sort/RowHeaderSortCallback.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.sort; import androidx.annotation.NonNull; import androidx.recyclerview.widget.DiffUtil; import java.util.List; /** * Created by cedricferry on 6/2/18. */ public class RowHeaderSortCallback extends DiffUtil.Callback { @NonNull private final List mOldCellItems; @NonNull private final List mNewCellItems; public RowHeaderSortCallback(@NonNull List oldCellItems, @NonNull List newCellItems) { this.mOldCellItems = oldCellItems; this.mNewCellItems = newCellItems; } @Override public int getOldListSize() { return mOldCellItems.size(); } @Override public int getNewListSize() { return mNewCellItems.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { // Control for precaution from IndexOutOfBoundsException if (mOldCellItems.size() > oldItemPosition && mNewCellItems.size() > newItemPosition) { // Compare ids String oldId = mOldCellItems.get(oldItemPosition).getId(); String newId = mNewCellItems.get(newItemPosition).getId(); return oldId.equals(newId); } return false; } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { // Control for precaution from IndexOutOfBoundsException if (mOldCellItems.size() > oldItemPosition && mNewCellItems.size() > newItemPosition) { // Compare contents Object oldContent = mOldCellItems.get(oldItemPosition) .getContent(); Object newContent = mNewCellItems.get(newItemPosition) .getContent(); return oldContent.equals(newContent); } return false; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/sort/RowHeaderSortComparator.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.sort; import androidx.annotation.NonNull; import java.util.Comparator; /** * Created by cedricferry on 6/2/18. */ public class RowHeaderSortComparator extends AbstractSortComparator implements Comparator { public RowHeaderSortComparator(@NonNull SortState sortState) { this.mSortState = sortState; } @Override public int compare(ISortableModel o1, ISortableModel o2) { if (mSortState == SortState.DESCENDING) { return compareContent(o2.getContent(), o1.getContent()); } else { return compareContent(o1.getContent(), o2.getContent()); } } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/sort/RowHeaderSortHelper.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.sort; import androidx.annotation.Nullable; /** * Created by cedricferry on 6/2/18. */ public class RowHeaderSortHelper { @Nullable private SortState mSortState; public RowHeaderSortHelper() { } private void sortingStatusChanged(@Nullable SortState sortState) { mSortState = sortState; // TODO: Should we add an interface and listener and call listener when it is sorted? } public void setSortingStatus(@Nullable SortState status) { mSortState = status; sortingStatusChanged(status); } public void clearSortingStatus() { mSortState = SortState.UNSORTED; } public boolean isSorting() { return mSortState != SortState.UNSORTED; } @Nullable public SortState getSortingStatus() { return mSortState; } } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/sort/SortState.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.sort; /** * Created by evrencoskun on 25.11.2017. */ public enum SortState { /** * Enumeration value indicating the items are sorted in increasing order. * For example, the set 1, 4, 0 sorted in * ASCENDING order is 0, 1, 4. */ ASCENDING, /** * Enumeration value indicating the items are sorted in decreasing order. * For example, the set 1, 4, 0 sorted in * DESCENDING order is 4, 1, 0. */ DESCENDING, /** * Enumeration value indicating the items are unordered. * For example, the set 1, 4, 0 in * UNSORTED order is 1, 4, 0. */ UNSORTED } ================================================ FILE: tableview/src/main/java/com/evrencoskun/tableview/util/TableViewUtils.java ================================================ /* * MIT License * * Copyright (c) 2021 Evren Coşkun * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.evrencoskun.tableview.util; import android.view.View; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; /** * Created by evrencoskun on 18/09/2017. */ public class TableViewUtils { /** * Helps to force width value before calling requestLayout by the system. */ public static void setWidth(@NonNull View view, int width) { // Change width value from params ((RecyclerView.LayoutParams) view.getLayoutParams()).width = width; int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(), View .MeasureSpec.EXACTLY); view.measure(widthMeasureSpec, heightMeasureSpec); view.requestLayout(); } } ================================================ FILE: tableview/src/main/res/drawable/cell_line_divider.xml ================================================ ================================================ FILE: tableview/src/main/res/values/attrs.xml ================================================ ================================================ FILE: tableview/src/main/res/values/colors.xml ================================================ #E7E7E7 #ffffff #fada65 #f2f2f2 ================================================ FILE: tableview/src/main/res/values/dimens.xml ================================================ 55dp 55dp ================================================ FILE: tableview/src/main/res/values/ids.xml ================================================ ================================================ FILE: tableview/src/main/res/values/integers.xml ================================================ 10