From 020841498c30e85857e1523a8dc35c8cecddb9c1 Mon Sep 17 00:00:00 2001 From: Farshid Roohi Date: Tue, 4 Oct 2022 16:32:44 +0330 Subject: [PATCH 1/5] Update Gradle and Libs --- build.gradle | 5 ++--- customAdapterRecycleView/build.gradle | 8 ++++---- gradle.properties | 3 +-- gradle/wrapper/gradle-wrapper.properties | 2 +- sample/build.gradle | 10 +++++----- sample/src/main/AndroidManifest.xml | 3 ++- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index 5392617..001b252 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.vanniktech:gradle-maven-publish-plugin:0.17.0" } diff --git a/customAdapterRecycleView/build.gradle b/customAdapterRecycleView/build.gradle index cd4afa3..fe57aea 100644 --- a/customAdapterRecycleView/build.gradle +++ b/customAdapterRecycleView/build.gradle @@ -4,11 +4,11 @@ apply plugin: 'maven-publish' apply plugin: 'com.vanniktech.maven.publish' android { - compileSdkVersion 30 + compileSdkVersion 33 defaultConfig { minSdkVersion 14 - targetSdkVersion 30 + targetSdkVersion 33 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -37,8 +37,8 @@ publishing { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'androidx.recyclerview:recyclerview:1.2.1' - implementation "androidx.core:core-ktx:1.6.0" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/gradle.properties b/gradle.properties index 56e978b..ca98292 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,5 +19,4 @@ POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt POM_LICENCE_DIST=repo POM_DEVELOPER_ID=farshidroohi -POM_DEVELOPER_NAME=Farshid Roohi - +POM_DEVELOPER_NAME=Farshid Roohi \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 67d4c2e..8d2a3af 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip diff --git a/sample/build.gradle b/sample/build.gradle index ebbff72..b354d85 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -3,10 +3,10 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' android { - compileSdkVersion 30 + compileSdkVersion 33 defaultConfig { minSdkVersion 14 - targetSdkVersion 30 + targetSdkVersion 33 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-rules.pro' @@ -23,10 +23,10 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation 'androidx.constraintlayout:constraintlayout:2.1.0' + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.recyclerview:recyclerview:1.2.1' - implementation "androidx.core:core-ktx:1.6.0" + implementation 'androidx.core:core-ktx:1.9.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation project(":customAdapterRecycleView") diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 15f7d78..0e305a6 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -9,7 +9,8 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - + From 08dc5a1edf96ae3ad901f3ff8529b03cd0c8b00f Mon Sep 17 00:00:00 2001 From: Farshid Roohi Date: Sat, 8 Oct 2022 14:44:29 +0330 Subject: [PATCH 2/5] Implement new RecyclerView Adapter - Support Multi View type - Update Samples --- customAdapterRecycleView/build.gradle | 8 + .../farshidroohi/AdapterRecyclerView.kt | 6 +- .../DiffUtilAdapterRecyclerView.kt | 54 ++++ .../farshidroohi/NewAdapterRecyclerView.kt | 274 ++++++++++++++++++ .../extensions/RecyclerViewExtensions.kt | 35 ++- .../github/farshidroohi/vh/BaseViewHolder.kt | 13 + sample/build.gradle | 9 + sample/src/main/AndroidManifest.xml | 17 +- .../customadapterrecyclerview/MainActivity.kt | 65 +---- .../customadapterrecyclerview/MyAdapter.kt | 19 -- .../multiViewType/MultiViewTypeActivity.kt | 93 ++++++ .../multiViewType/MultiViewTypeAdapter.kt | 119 ++++++++ .../singleViewType/SingleViewTypeActivity.kt | 73 +++++ .../singleViewType/SingleViewTypeAdapter.kt | 30 ++ sample/src/main/res/layout/activity_main.xml | 24 +- .../res/layout/activity_multi_view_type.xml | 15 + .../res/layout/activity_single_view_type.xml | 15 + sample/src/main/res/layout/item_four.xml | 18 ++ sample/src/main/res/layout/item_three.xml | 18 ++ sample/src/main/res/layout/item_two.xml | 18 ++ sample/src/main/res/layout/my_item.xml | 17 +- sample/src/main/res/values/strings.xml | 3 + 22 files changed, 834 insertions(+), 109 deletions(-) create mode 100644 customAdapterRecycleView/src/main/java/io/github/farshidroohi/DiffUtilAdapterRecyclerView.kt create mode 100644 customAdapterRecycleView/src/main/java/io/github/farshidroohi/NewAdapterRecyclerView.kt create mode 100644 customAdapterRecycleView/src/main/java/io/github/farshidroohi/vh/BaseViewHolder.kt delete mode 100644 sample/src/main/java/io/github/customadapterrecyclerview/MyAdapter.kt create mode 100644 sample/src/main/java/io/github/customadapterrecyclerview/multiViewType/MultiViewTypeActivity.kt create mode 100644 sample/src/main/java/io/github/customadapterrecyclerview/multiViewType/MultiViewTypeAdapter.kt create mode 100644 sample/src/main/java/io/github/customadapterrecyclerview/singleViewType/SingleViewTypeActivity.kt create mode 100644 sample/src/main/java/io/github/customadapterrecyclerview/singleViewType/SingleViewTypeAdapter.kt create mode 100644 sample/src/main/res/layout/activity_multi_view_type.xml create mode 100644 sample/src/main/res/layout/activity_single_view_type.xml create mode 100644 sample/src/main/res/layout/item_four.xml create mode 100644 sample/src/main/res/layout/item_three.xml create mode 100644 sample/src/main/res/layout/item_two.xml diff --git a/customAdapterRecycleView/build.gradle b/customAdapterRecycleView/build.gradle index fe57aea..1423ad0 100644 --- a/customAdapterRecycleView/build.gradle +++ b/customAdapterRecycleView/build.gradle @@ -20,6 +20,14 @@ android { } } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + buildFeatures { + viewBinding = true + } + } publishing { diff --git a/customAdapterRecycleView/src/main/java/io/github/farshidroohi/AdapterRecyclerView.kt b/customAdapterRecycleView/src/main/java/io/github/farshidroohi/AdapterRecyclerView.kt index 2561d8b..0afd8fa 100644 --- a/customAdapterRecycleView/src/main/java/io/github/farshidroohi/AdapterRecyclerView.kt +++ b/customAdapterRecycleView/src/main/java/io/github/farshidroohi/AdapterRecyclerView.kt @@ -1,5 +1,6 @@ package io.github.farshidroohi +import android.annotation.SuppressLint import android.content.Context import android.view.LayoutInflater import android.view.View @@ -125,6 +126,7 @@ abstract class AdapterRecyclerView( } + @SuppressLint("NotifyDataSetChanged") fun removeAll() { if (items.isEmpty()) { return @@ -142,11 +144,12 @@ abstract class AdapterRecyclerView( notifyItemRangeChanged(position, itemCount) } + @SuppressLint("NotifyDataSetChanged") fun remove(vararg item: T) { if (this.items.isEmpty()) { return } - this.items.removeAll(item) + this.items.removeAll(item.toSet()) notifyDataSetChanged() } @@ -155,6 +158,7 @@ abstract class AdapterRecyclerView( } + @SuppressLint("NotifyDataSetChanged") fun loadedState(newItems: List?) { if (newItems == null) { diff --git a/customAdapterRecycleView/src/main/java/io/github/farshidroohi/DiffUtilAdapterRecyclerView.kt b/customAdapterRecycleView/src/main/java/io/github/farshidroohi/DiffUtilAdapterRecyclerView.kt new file mode 100644 index 0000000..5bd1fe9 --- /dev/null +++ b/customAdapterRecycleView/src/main/java/io/github/farshidroohi/DiffUtilAdapterRecyclerView.kt @@ -0,0 +1,54 @@ +package io.github.farshidroohi + +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.viewbinding.ViewBinding +import io.github.farshidroohi.vh.BaseViewHolder + + +/** + * Created by Farshid Roohi. + * CustomAdapterRecyclerview | Copyrights 10/8/22. + */ + +abstract class DiffUtilAdapterRecyclerView( + diffCallback: DiffUtil.ItemCallback +) : ListAdapter>(diffCallback) { + + abstract fun initViewBinding(parent: ViewGroup): ViewBindingType + abstract fun bind(binding: ViewBindingType, position: Int, item: T, payloads: MutableList?) + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): BaseViewHolder { + val binding = initViewBinding(parent) + return BaseViewHolder(binding) + } + + override fun onBindViewHolder( + holder: BaseViewHolder, + position: Int, + payloads: MutableList + ) { + bind(holder.binding, position, currentList[position], payloads) + } + + override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { + bind(holder.binding, position, currentList[position], null) + } + + override fun getItemCount(): Int = currentList.size + + fun changeItem(items: ArrayList, element: T, newElement: T) { + val index = items.indexOf(element) + items[index] = newElement + submitList(items.toMutableList()) + } + + fun clear() { + submitList(emptyList()) + } + +} \ No newline at end of file diff --git a/customAdapterRecycleView/src/main/java/io/github/farshidroohi/NewAdapterRecyclerView.kt b/customAdapterRecycleView/src/main/java/io/github/farshidroohi/NewAdapterRecyclerView.kt new file mode 100644 index 0000000..cbac7dd --- /dev/null +++ b/customAdapterRecycleView/src/main/java/io/github/farshidroohi/NewAdapterRecyclerView.kt @@ -0,0 +1,274 @@ +package io.github.farshidroohi + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.IdRes +import androidx.annotation.LayoutRes +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.StaggeredGridLayoutManager + + +/** + * Created by Farshid Roohi. + * CustomAdapterRecyclerView | Copyrights 1/1/19. + */ + +abstract class NewAdapterRecyclerView( + @LayoutRes val itemViewLayout: Int, + @LayoutRes val itemLoadingLayout: Int, + @LayoutRes val itemFailedLayout: Int, + @IdRes val retryButtonId: Int, +) : RecyclerView.Adapter() { + + companion object { + const val ITEM_VIEW = 0 + const val ITEM_LOADING = 1 + const val ITEM_FAILED = 2 + } + + val items: MutableList = arrayListOf() + private var lastItemItemState: ItemState = ItemState.LOADED + private var layoutManager: RecyclerView.LayoutManager? = null + var mustLoad: Boolean = false + private set + get() = lastItemItemState == ItemState.LOADED + var onRetryClicked: () -> Unit = {} + + + abstract fun onBindView( + viewHolder: RecyclerView.ViewHolder, + position: Int, + context: Context, + element: T? + ) + + open fun onCreateViewHolder( + layoutInflater: LayoutInflater, + viewGroup: ViewGroup, + viewType: Int + ): RecyclerView.ViewHolder? { + return null + } + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + + val inflater = LayoutInflater.from(viewGroup.context) + + return when (viewType) { + + ITEM_VIEW -> { + val view = inflater.inflate(itemViewLayout, viewGroup, false) + ItemViewHolder(view) + } + + ITEM_LOADING -> { + val view = inflater.inflate(itemLoadingLayout, viewGroup, false) + LoadingViewHolder(view) + } + + ITEM_FAILED -> { + val view = inflater.inflate(itemFailedLayout, viewGroup, false) + FailedViewHolder(view) + } + else -> onCreateViewHolder(inflater, viewGroup, viewType)!! + } + + } + + override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) { + + when (viewHolder) { + + is FailedViewHolder -> { + + handleSingleRow(viewHolder, position) + + val view = viewHolder.itemView.findViewById(retryButtonId) + view?.setOnClickListener { + onRetryClicked() + } + } + + is LoadingViewHolder -> { + handleSingleRow(viewHolder, position) + } + else -> { + onBindView(viewHolder, position, viewHolder.itemView.context, getItem(position)) + } + + } + + } + + + override fun getItemCount(): Int { + return items.size + } + + override fun getItemViewType(position: Int): Int { + + if (position == itemCount - 1) { + + if (lastItemItemState == ItemState.FAILED) { + return ITEM_FAILED + } + + if (lastItemItemState == ItemState.LOADING) { + return ITEM_LOADING + } + + } + + return ITEM_VIEW + + } + + + @SuppressLint("NotifyDataSetChanged") + fun removeAll() { + if (items.isEmpty()) { + return + } + items.clear() + notifyDataSetChanged() + } + + fun remove(position: Int) { + if (this.items.isEmpty() || position > this.items.lastIndex) { + return + } + this.items.removeAt(position) + notifyItemRemoved(position) + notifyItemRangeChanged(position, itemCount) + } + + @SuppressLint("NotifyDataSetChanged") + fun remove(vararg item: T) { + if (this.items.isEmpty()) { + return + } + this.items.removeAll(item.toSet()) + notifyDataSetChanged() + } + + fun getItem(position: Int): T? { + return if (items.isEmpty()) null else items[position] + } + + + @SuppressLint("NotifyDataSetChanged") + fun loadedState(newItems: List?) { + + if (newItems == null) { + return + } + + var addedPosition = 0 + + if (items.isNotEmpty()) { + addedPosition = itemCount + if (lastItemItemState != ItemState.LOADED) { + items.removeAt(items.lastIndex) + } + } + + lastItemItemState = ItemState.LOADED + items.addAll(newItems) + + if (addedPosition == 0) { + notifyDataSetChanged() + return + } + notifyItemInserted(addedPosition) + + } + + fun failedState() { + + if (lastItemItemState == ItemState.LOADING) { + items.removeAt(items.lastIndex) + } + + lastItemItemState = ItemState.FAILED + items.add(null) + + notifyItemChanged(items.lastIndex) + + } + + + fun loadingState() { + + var changed = false + + if (items.isNotEmpty() && lastItemItemState != ItemState.LOADED) { + items.removeAt(items.lastIndex) + changed = true + } + + lastItemItemState = ItemState.LOADING + items.add(null) + + if (changed) { + notifyItemChanged(items.lastIndex) + return + } + notifyItemInserted(items.lastIndex) + } + + + private fun handleSingleRow(viewHolder: RecyclerView.ViewHolder, position: Int) { + + if (layoutManager is StaggeredGridLayoutManager) { + handleSingleRowStaggered(viewHolder) + } + + if (layoutManager is GridLayoutManager) { + handleSingleRowGrid(position) + } + + } + + // When we use the grid, change span size because show progressView single row + private fun handleSingleRowGrid(index: Int) { + if (layoutManager != null && layoutManager is GridLayoutManager) { + val gridLayoutManager = layoutManager as GridLayoutManager + val spanCount = gridLayoutManager.spanCount + gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if ((lastItemItemState == ItemState.LOADING || lastItemItemState == ItemState.FAILED) && position == index && items[index] == null) { + spanCount + } else 1 + } + } + } + } + + // When we use the staggered, change span size because show progressView single row + private fun handleSingleRowStaggered(viewHolder: RecyclerView.ViewHolder) { + val layoutParams = + viewHolder.itemView.layoutParams as StaggeredGridLayoutManager.LayoutParams + layoutParams.isFullSpan = lastItemItemState == ItemState.LOADING + } + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + layoutManager = recyclerView.layoutManager + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + super.onDetachedFromRecyclerView(recyclerView) + layoutManager = null + } + + class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) + + class LoadingViewHolder(view: View) : RecyclerView.ViewHolder(view) + + class FailedViewHolder(view: View) : RecyclerView.ViewHolder(view) + +} \ No newline at end of file diff --git a/customAdapterRecycleView/src/main/java/io/github/farshidroohi/extensions/RecyclerViewExtensions.kt b/customAdapterRecycleView/src/main/java/io/github/farshidroohi/extensions/RecyclerViewExtensions.kt index b42e897..fab3986 100644 --- a/customAdapterRecycleView/src/main/java/io/github/farshidroohi/extensions/RecyclerViewExtensions.kt +++ b/customAdapterRecycleView/src/main/java/io/github/farshidroohi/extensions/RecyclerViewExtensions.kt @@ -34,10 +34,10 @@ fun RecyclerView.onLoadMoreListener(extraCount: Int = 0, onLoadMore: () -> Unit) is StaggeredGridLayoutManager -> { val staggeredGridLayoutManager = - layoutManager as StaggeredGridLayoutManager + layoutManager as StaggeredGridLayoutManager val spanCount = staggeredGridLayoutManager.spanCount val lastPositions = - staggeredGridLayoutManager.findFirstVisibleItemPositions(IntArray(spanCount)) + staggeredGridLayoutManager.findFirstVisibleItemPositions(IntArray(spanCount)) min(lastPositions[0], lastPositions[1]) } @@ -61,16 +61,29 @@ fun RecyclerView.onLoadMoreListener(extraCount: Int = 0, onLoadMore: () -> Unit) } -fun RecyclerView.onItemClickListener(onClickItem: (position: Int) -> Unit, onLongClickItem: (position: Int) -> Unit) { +fun RecyclerView.onItemClickListener( + onClickItem: (position: Int) -> Unit, + onLongClickItem: ((position: Int) -> Unit)? = null +) { + + this.addOnItemTouchListener( + RecyclerTouchListener( + context, + this, + object : OnItemListenerRecyclerViewListener { + override fun onClick(position: Int) { + if (position != (adapter?.itemCount ?: 0) - 1) { + onClickItem(position) + } + } - this.addOnItemTouchListener(RecyclerTouchListener(context, this, object : OnItemListenerRecyclerViewListener { - override fun onClick(position: Int) { - onClickItem(position) - } + override fun onLongClick(position: Int) { + if (position != (adapter?.itemCount ?: 0) - 1) { + onLongClickItem?.invoke(position) + } - override fun onLongClick(position: Int) { - onLongClickItem(position) - } - })) + } + }) + ) } \ No newline at end of file diff --git a/customAdapterRecycleView/src/main/java/io/github/farshidroohi/vh/BaseViewHolder.kt b/customAdapterRecycleView/src/main/java/io/github/farshidroohi/vh/BaseViewHolder.kt new file mode 100644 index 0000000..a75f946 --- /dev/null +++ b/customAdapterRecycleView/src/main/java/io/github/farshidroohi/vh/BaseViewHolder.kt @@ -0,0 +1,13 @@ +package io.github.farshidroohi.vh + +import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding + +/** + * Created by Farshid Roohi. + * CustomAdapterRecyclerview | Copyrights 10/8/22. + */ + +class BaseViewHolder(val binding: ViewBindingType) : + RecyclerView.ViewHolder(binding.root) { +} \ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle index b354d85..95550b1 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -19,6 +19,15 @@ android { } } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildFeatures { + viewBinding = true + } + } dependencies { diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 0e305a6..fa37012 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + package="ir.farshid_roohi.customadapterrecyclerview"> - - + - + + + + + \ No newline at end of file diff --git a/sample/src/main/java/io/github/customadapterrecyclerview/MainActivity.kt b/sample/src/main/java/io/github/customadapterrecyclerview/MainActivity.kt index 3b91f2c..301769f 100644 --- a/sample/src/main/java/io/github/customadapterrecyclerview/MainActivity.kt +++ b/sample/src/main/java/io/github/customadapterrecyclerview/MainActivity.kt @@ -1,14 +1,13 @@ package io.github.customadapterrecyclerview +import android.annotation.SuppressLint +import android.content.Intent import android.os.Bundle -import android.widget.Toast +import android.widget.Button import androidx.appcompat.app.AppCompatActivity -import androidx.recyclerview.widget.GridLayoutManager -import io.github.farshidroohi.extensions.onItemClickListener -import io.github.farshidroohi.extensions.onLoadMoreListener +import io.github.customadapterrecyclerview.multiViewType.MultiViewTypeActivity +import io.github.customadapterrecyclerview.singleViewType.SingleViewTypeActivity import ir.farshid_roohi.customadapterrecyclerview.R -import kotlinx.android.synthetic.main.activity_main.* -import java.util.* class MainActivity : AppCompatActivity() { @@ -16,58 +15,12 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - val myAdapter = MyAdapter() - - // onClick Error Button - myAdapter.onRetryClicked = { - myAdapter.loadingState() - // fake request or load other items... - recyclerView.postDelayed({ - myAdapter.loadedState(tempItems) - }, 2000) + findViewById