diff --git a/CHANGELOG.md b/CHANGELOG.md index e7689f3..48b1cfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,12 @@ Change Log ========== -## Version 2.0.0, 2.0.1 *(2021-05-22)* 🚀 +## Version 2.0.2 *(2021-05-31)* 🚀 + +* 🆕 `registerLifecycle(lifecycleOwner: LifecycleOwner)` method added. +* 🛠️ Java sample converted into a `Fragment` example. + +## Version 2.0.0, 2.0.1 *(2021-05-22)* * 🆕 Previous boring custom layout system removed. And view-binding supported custom layout system added. See `CarouselListener` in the sample app for details. 🎉 * 🆕 The carousel is now supported Infinite ∞ looping (Infinite Carousel) 🥳! It's default now. You can disable it by setting `infiniteCarousel` to `false`. diff --git a/README.md b/README.md index 5242332..b0f268f 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ dependencies { // Material Components for Android. Replace the version with the latest version of Material Components library. implementation 'com.google.android.material:material:1.3.0' - // Optional: Circle Indicator (To fix the xml preview "Missing classes" error) + // Circle Indicator (To fix the xml preview "Missing classes" error) implementation 'me.relex:circleindicator:2.1.6' - implementation 'org.imaginativeworld.whynotimagecarousel:whynotimagecarousel:2.0.1' + implementation 'org.imaginativeworld.whynotimagecarousel:whynotimagecarousel:2.0.2' } ``` diff --git a/gradle.properties b/gradle.properties index 63feb68..c0221cc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,7 +24,7 @@ kotlin.code.style=official # ---------------- GROUP=org.imaginativeworld.whynotimagecarousel POM_ARTIFACT_ID=whynotimagecarousel -VERSION_NAME=2.0.1 +VERSION_NAME=2.0.2 # ---------------- POM_NAME=Why Not! Image Carousel! POM_DESCRIPTION=An easy, super simple and customizable image carousel view for Android. diff --git a/sample/build.gradle b/sample/build.gradle index b0f7c36..905fa27 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -65,6 +65,9 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.12.0' kapt 'com.github.bumptech.glide:compiler:4.12.0' + // Kotlin + implementation "androidx.fragment:fragment-ktx:1.3.4" + // debugImplementation because LeakCanary should only run in debug builds. debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 79ac768..43f4a87 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -9,7 +9,6 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - @@ -17,6 +16,8 @@ + + \ No newline at end of file diff --git a/sample/src/main/java/org/imaginativeworld/whynotimagecarousel/sample/JavaActivity.java b/sample/src/main/java/org/imaginativeworld/whynotimagecarousel/sample/JavaActivity.java index e9b2f77..c764073 100644 --- a/sample/src/main/java/org/imaginativeworld/whynotimagecarousel/sample/JavaActivity.java +++ b/sample/src/main/java/org/imaginativeworld/whynotimagecarousel/sample/JavaActivity.java @@ -1,187 +1,19 @@ package org.imaginativeworld.whynotimagecarousel.sample; -import android.content.Context; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.ViewGroup; -import android.widget.ImageView; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.RecyclerView; -import androidx.viewbinding.ViewBinding; -import com.google.android.material.button.MaterialButton; - -import org.imaginativeworld.whynotimagecarousel.listener.CarouselListener; -import org.imaginativeworld.whynotimagecarousel.listener.CarouselOnScrollListener; -import org.imaginativeworld.whynotimagecarousel.model.CarouselGravity; -import org.imaginativeworld.whynotimagecarousel.model.CarouselItem; -import org.imaginativeworld.whynotimagecarousel.model.CarouselType; import org.imaginativeworld.whynotimagecarousel.sample.databinding.ActivityJavaBinding; -import org.imaginativeworld.whynotimagecarousel.utils.Utils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import me.relex.circleindicator.CircleIndicator2; public class JavaActivity extends AppCompatActivity { private ActivityJavaBinding binding; - private Context context; - - private boolean isStarted = false; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityJavaBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - - context = this; - - binding.carousel.registerLifecycle(getLifecycle()); - - binding.carousel.setShowTopShadow(false); - binding.carousel.setTopShadowAlpha(0.6f); // 0 to 1, 1 means 100% - binding.carousel.setTopShadowHeight(Utils.dpToPx(32, context)); // px value of dp - - binding.carousel.setShowBottomShadow(true); - binding.carousel.setBottomShadowAlpha(0.7f); // 0 to 1, 1 means 100% - binding.carousel.setBottomShadowHeight(Utils.dpToPx(48, context)); // px value of dp - - binding.carousel.setShowCaption(true); - binding.carousel.setCaptionMargin(Utils.dpToPx(8, context)); // px value of dp - binding.carousel.setCaptionTextSize(Utils.spToPx(16, context)); // px value of sp - - binding.carousel.setShowIndicator(false); - binding.carousel.setIndicatorMargin(Utils.dpToPx(0, context)); // px value of dp - - binding.carousel.setImageScaleType(ImageView.ScaleType.CENTER_CROP); - - binding.carousel.setCarouselBackground(new ColorDrawable(Color.parseColor("#333333"))); - binding.carousel.setImagePlaceholder(ContextCompat.getDrawable( - this, - R.drawable.ic_wb_cloudy_with_padding - )); - - binding.carousel.setCarouselPadding(Utils.dpToPx(0, context)); - binding.carousel.setCarouselPaddingStart(Utils.dpToPx(0, context)); - binding.carousel.setCarouselPaddingTop(Utils.dpToPx(0, context)); - binding.carousel.setCarouselPaddingEnd(Utils.dpToPx(0, context)); - binding.carousel.setCarouselPaddingBottom(Utils.dpToPx(0, context)); - - binding.carousel.setShowNavigationButtons(false); - binding.carousel.setPreviousButtonLayout(R.layout.custom_previous_button_layout); - binding.carousel.setPreviousButtonId(R.id.custom_btn_previous); - binding.carousel.setPreviousButtonMargin(Utils.dpToPx(8, context)); // px value of dp - binding.carousel.setNextButtonLayout(R.layout.custom_next_button_layout); - binding.carousel.setNextButtonId(R.id.custom_btn_next); - binding.carousel.setNextButtonMargin(Utils.dpToPx(8, context)); // px value of dp - - binding.carousel.setCarouselType(CarouselType.SHOWCASE); - - binding.carousel.setCarouselGravity(CarouselGravity.CENTER); - - binding.carousel.setScaleOnScroll(false); - binding.carousel.setScalingFactor(.15f); - binding.carousel.setAutoWidthFixing(true); - binding.carousel.setAutoPlay(false); - binding.carousel.setAutoPlayDelay(3000); // Milliseconds - binding.carousel.setInfiniteCarousel(true); - binding.carousel.setTouchToPause(true); - - binding.carousel.setOnScrollListener(new CarouselOnScrollListener() { - @Override - public void onScrolled(@NotNull RecyclerView recyclerView, int dx, int dy, int position, @Nullable CarouselItem carouselItem) { - // ... - } - - @Override - public void onScrollStateChanged(@NotNull RecyclerView recyclerView, int newState, int position, @Nullable CarouselItem carouselItem) { - // ... - } - }); - - binding.carousel.setCarouselListener(new CarouselListener() { - @Nullable - @Override - public ViewBinding onCreateViewHolder(@NotNull LayoutInflater layoutInflater, @NotNull ViewGroup parent) { - // ... - return null; - } - - @Override - public void onBindViewHolder(@NotNull ViewBinding binding, @NotNull CarouselItem item, int position) { - // ... - } - - @Override - public void onLongClick(int position, @NotNull CarouselItem carouselItem) { - // ... - } - - @Override - public void onClick(int position, @NotNull CarouselItem carouselItem) { - // ... - } - }); - - CircleIndicator2 indicator = findViewById(R.id.custom_indicator); - binding.carousel.setIndicator(indicator); - - MaterialButton previousBtn = findViewById(R.id.btn_goto_previous); - previousBtn.setOnClickListener(v -> binding.carousel.previous()); - - MaterialButton nextBtn = findViewById(R.id.btn_goto_next); - nextBtn.setOnClickListener(v -> binding.carousel.next()); - - List list = new ArrayList<>(); - - // Dummy header - Map headers = new HashMap<>(); - headers.put("header_key", "header_value"); - - int index = 1; - for (String item : DataSet.INSTANCE.getOne()) { - list.add( - new CarouselItem( - item, - "Image " + index++ + " of " + DataSet.INSTANCE.getOne().size(), - headers - ) - ); - } - - binding.carousel.setData(list); - - // ---------------------------------------------------------------- - - binding.fabPlay.setOnClickListener(v -> { - if (isStarted) { - - isStarted = false; - binding.carousel.stop(); - - binding.fabPlay.setText("Start"); - - } else { - - isStarted = true; - binding.carousel.start(); - - binding.fabPlay.setText("Stop"); - - } - }); - } } diff --git a/sample/src/main/java/org/imaginativeworld/whynotimagecarousel/sample/SampleFragment.java b/sample/src/main/java/org/imaginativeworld/whynotimagecarousel/sample/SampleFragment.java new file mode 100644 index 0000000..87eb147 --- /dev/null +++ b/sample/src/main/java/org/imaginativeworld/whynotimagecarousel/sample/SampleFragment.java @@ -0,0 +1,194 @@ +package org.imaginativeworld.whynotimagecarousel.sample; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +import com.google.android.material.button.MaterialButton; + +import org.imaginativeworld.whynotimagecarousel.listener.CarouselListener; +import org.imaginativeworld.whynotimagecarousel.listener.CarouselOnScrollListener; +import org.imaginativeworld.whynotimagecarousel.model.CarouselGravity; +import org.imaginativeworld.whynotimagecarousel.model.CarouselItem; +import org.imaginativeworld.whynotimagecarousel.model.CarouselType; +import org.imaginativeworld.whynotimagecarousel.sample.databinding.FragmentSampleBinding; +import org.imaginativeworld.whynotimagecarousel.utils.Utils; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import me.relex.circleindicator.CircleIndicator2; + +public class SampleFragment extends Fragment { + + private FragmentSampleBinding binding; + + private Context context; + + private boolean isStarted = false; + + public SampleFragment() { + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = FragmentSampleBinding.inflate(getLayoutInflater(), container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + context = requireContext(); + + binding.carousel.registerLifecycle(getViewLifecycleOwner()); + + binding.carousel.setShowTopShadow(false); + binding.carousel.setTopShadowAlpha(0.6f); // 0 to 1, 1 means 100% + binding.carousel.setTopShadowHeight(Utils.dpToPx(32, context)); // px value of dp + + binding.carousel.setShowBottomShadow(true); + binding.carousel.setBottomShadowAlpha(0.7f); // 0 to 1, 1 means 100% + binding.carousel.setBottomShadowHeight(Utils.dpToPx(48, context)); // px value of dp + + binding.carousel.setShowCaption(true); + binding.carousel.setCaptionMargin(Utils.dpToPx(8, context)); // px value of dp + binding.carousel.setCaptionTextSize(Utils.spToPx(16, context)); // px value of sp + + binding.carousel.setShowIndicator(false); + binding.carousel.setIndicatorMargin(Utils.dpToPx(0, context)); // px value of dp + + binding.carousel.setImageScaleType(ImageView.ScaleType.CENTER_CROP); + + binding.carousel.setCarouselBackground(new ColorDrawable(Color.parseColor("#333333"))); + binding.carousel.setImagePlaceholder(ContextCompat.getDrawable( + context, + R.drawable.ic_wb_cloudy_with_padding + )); + + binding.carousel.setCarouselPadding(Utils.dpToPx(0, context)); + binding.carousel.setCarouselPaddingStart(Utils.dpToPx(0, context)); + binding.carousel.setCarouselPaddingTop(Utils.dpToPx(0, context)); + binding.carousel.setCarouselPaddingEnd(Utils.dpToPx(0, context)); + binding.carousel.setCarouselPaddingBottom(Utils.dpToPx(0, context)); + + binding.carousel.setShowNavigationButtons(false); + binding.carousel.setPreviousButtonLayout(R.layout.custom_previous_button_layout); + binding.carousel.setPreviousButtonId(R.id.custom_btn_previous); + binding.carousel.setPreviousButtonMargin(Utils.dpToPx(8, context)); // px value of dp + binding.carousel.setNextButtonLayout(R.layout.custom_next_button_layout); + binding.carousel.setNextButtonId(R.id.custom_btn_next); + binding.carousel.setNextButtonMargin(Utils.dpToPx(8, context)); // px value of dp + + binding.carousel.setCarouselType(CarouselType.SHOWCASE); + + binding.carousel.setCarouselGravity(CarouselGravity.CENTER); + + binding.carousel.setScaleOnScroll(false); + binding.carousel.setScalingFactor(.15f); + binding.carousel.setAutoWidthFixing(true); + binding.carousel.setAutoPlay(false); + binding.carousel.setAutoPlayDelay(3000); // Milliseconds + binding.carousel.setInfiniteCarousel(true); + binding.carousel.setTouchToPause(true); + + binding.carousel.setOnScrollListener(new CarouselOnScrollListener() { + @Override + public void onScrolled(@NotNull RecyclerView recyclerView, int dx, int dy, int position, @org.jetbrains.annotations.Nullable CarouselItem carouselItem) { + // ... + } + + @Override + public void onScrollStateChanged(@NotNull RecyclerView recyclerView, int newState, int position, @org.jetbrains.annotations.Nullable CarouselItem carouselItem) { + // ... + } + }); + + binding.carousel.setCarouselListener(new CarouselListener() { + @org.jetbrains.annotations.Nullable + @Override + public ViewBinding onCreateViewHolder(@NotNull LayoutInflater layoutInflater, @NotNull ViewGroup parent) { + // ... + return null; + } + + @Override + public void onBindViewHolder(@NotNull ViewBinding binding, @NotNull CarouselItem item, int position) { + // ... + } + + @Override + public void onLongClick(int position, @NotNull CarouselItem carouselItem) { + // ... + } + + @Override + public void onClick(int position, @NotNull CarouselItem carouselItem) { + // ... + } + }); + + CircleIndicator2 indicator = binding.customIndicator; + binding.carousel.setIndicator(indicator); + + MaterialButton previousBtn = binding.btnGotoPrevious; + previousBtn.setOnClickListener(v -> binding.carousel.previous()); + + MaterialButton nextBtn = binding.btnGotoNext; + nextBtn.setOnClickListener(v -> binding.carousel.next()); + + List list = new ArrayList<>(); + + // Dummy header + Map headers = new HashMap<>(); + headers.put("header_key", "header_value"); + + int index = 1; + for (String item : DataSet.INSTANCE.getOne()) { + list.add( + new CarouselItem( + item, + "Image " + index++ + " of " + DataSet.INSTANCE.getOne().size(), + headers + ) + ); + } + + binding.carousel.setData(list); + + // ---------------------------------------------------------------- + + binding.fabPlay.setOnClickListener(v -> { + if (isStarted) { + + isStarted = false; + binding.carousel.stop(); + + binding.fabPlay.setText("Start"); + + } else { + + isStarted = true; + binding.carousel.start(); + + binding.fabPlay.setText("Stop"); + + } + }); + } +} \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_java.xml b/sample/src/main/res/layout/activity_java.xml index e0324fc..4f532ff 100644 --- a/sample/src/main/res/layout/activity_java.xml +++ b/sample/src/main/res/layout/activity_java.xml @@ -1,110 +1,15 @@ - + android:layout_height="match_parent" + tools:layout="@layout/fragment_sample" /> - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + diff --git a/sample/src/main/res/layout/fragment_sample.xml b/sample/src/main/res/layout/fragment_sample.xml new file mode 100644 index 0000000..5563b96 --- /dev/null +++ b/sample/src/main/res/layout/fragment_sample.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml index ef5923c..7235824 100644 --- a/sample/src/main/res/values/styles.xml +++ b/sample/src/main/res/values/styles.xml @@ -20,4 +20,9 @@ textStart + + diff --git a/whynotimagecarousel/src/main/java/org/imaginativeworld/whynotimagecarousel/ImageCarousel.kt b/whynotimagecarousel/src/main/java/org/imaginativeworld/whynotimagecarousel/ImageCarousel.kt index 4910a48..956eb36 100644 --- a/whynotimagecarousel/src/main/java/org/imaginativeworld/whynotimagecarousel/ImageCarousel.kt +++ b/whynotimagecarousel/src/main/java/org/imaginativeworld/whynotimagecarousel/ImageCarousel.kt @@ -22,6 +22,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.OnLifecycleEvent import androidx.recyclerview.widget.* import me.relex.circleindicator.CircleIndicator2 @@ -1114,6 +1115,17 @@ class ImageCarousel( lifecycle.addObserver(this) } + /** + * It receives lifecycle owner as a parameter, especially for fragments. + * + * @see [registerLifecycle] for details. + * + * @param lifecycleOwner A [androidx.lifecycle.LifecycleOwner] + */ + fun registerLifecycle(lifecycleOwner: LifecycleOwner) { + lifecycleOwner.lifecycle.addObserver(this) + } + // ---------------------------------------------------------------- /**