Giter Club home page Giter Club logo

spannedgridlayoutmanager's Introduction

Download API License: MIT

SpannedGridLayoutManager

SpannedGridLayouManager is a layout manager that will resize and reorder views based on a provided SpanSize.

IMPORTANT: as the goal of this LayoutManager is to fill all gaps if possible, views may be placed in an order which may not be related to their positions. That is, if View #9 can fill a gap before View #8, it will be placed in an previous position, ignoring the normal ordering.

video

Usage

Gradle dependency:

dependencies {
	implementation 'com.arasthel:spannedgridlayoutmanager:3.0.2'
}

When you create a new SpannedGridLayoutManager you must provide:

  • An Orientation: Orientation.VERTICAL or Orientation.HORIZONTAL.
  • The span count to divide the layout.

Kotlin Example:

val spannedGridLayoutManager = SpannedGridLayoutManager(
                orientation = SpannedGridLayoutManager.Orientation.VERTICAL, 
                spans = 4)

Java Example:

SpannedGridLayoutManager spannedGridLayoutManager = new SpannedGridLayoutManager(
			SpannedGridLayoutManager.Orientation.VERTICAL, 4);

To set the SpanSize for each view, you need to use SpannedGridLayoutManager.SpanSizeLookup. This class has a lambda that receives an adapter position and must return an SpanSize:

Kotlin:

spannedGridLayoutManager.spanSizeLookup = SpannedGridLayoutManager.SpanSizeLookUp { position -> 
    SpanSize(2, 2)
}
recyclerview.layoutManager = spannedGridLayoutManager

Java:

// If your project has Java 8 support for lambdas
spannedGridLayoutManager.setSpanSizeLookup(new SpannedGridLayoutManager.SpanSizeLookup({ position ->
    new SpanSize(2, 2);
}));

// If your project uses Java 6 or 7. Yup, it's ugly as hell
spannedGridLayoutManager.setSpanSizeLookup(new SpannedGridLayoutManager.SpanSizeLookup(new Function1<Integer, SpanSize>(){
  @Override public SpanSize invoke(Integer position) {
    return new SpanSize(2, 2);
  }
}));

recyclerview.setLayoutManager(spannedGridLayoutManager);

If the logic needed to calculate this values is very complex, they can be cached by setting spanSizeLookup.usesCache = true or spanSizeLookup.setUsesCache(true). If you need to invalidate them, just call spanSizeLookup.invalidateCache().

About restoring scroll position:

As this LayoutManager may change the order in screen of its items, it has some issues with restoring scroll position when the sizes of the items change drastically. To work around this, restoring the scroll position when recreating this LayoutManager is disabled by default.

If you are fairly sure that the position of the items won't change much, you can enable it again using:

spannedLayoutManager.itemOrderIsStable = true

Migrating from 1.X to 2.X

Due to critical layout issues, the API for using SpanSizes had to change. The only changes you should have to do in your code are:

    val width = 1
    val height = 2

    // OLD
    // holder.itemView.layoutParams = RecyclerView.LayoutParams(width, height)

    // NEW
    holder.itemView.layoutParams = SpanLayoutParams(SpanSize(width, height))

Just use the new SpanLayoutParams instead of generic RecyclerView.LayoutParams.

Migrating from 2.X to 3.X

Sadly, due to more limitations of the initial design and bugs, most of the LayoutManager's layouting and recycling process had to be re-written. This depended on some breaking API changes since otherwise there would have been lots of unnecessary recycling of views and performance loss when the spans are calculated.

Because of this, while you would set the SpanSizes using SpanLayoutParams, these are no longer needed. You can safely delete those from your adapter.

Instead of that, you must use the SpannedGridLayoutManager.SpanSizeLookup like you would do with a GridLayoutManager. You can find more info on the Usage section.

Animations

To have animations as shown in the sample, you must:

  • Use setHasStableIds(true) in your adapter.
  • Override getItemId method to return a stable id - that is, it won't change for the same position.
  • Use adapter.notifyDatasetChanged() to trigger the layout process.

Implementation details

This library uses Rects to find the empty gaps and choose where a view will be placed. The underlying algorithm is explained in this paper.

  • Initially, there will be 1 free-space Rect (0, 0, spanCount, Int.MAX_VALUE) for Orientation.VERTICAL or (0, 0, Int.MAX_VALUE, spanCount) for Orientation.HORIZONTAL.
  • When a view must added, it will search for the free rects that are adjacent to the view's Rect or that intersects it.
  • It will iterate over these rects looking for those that are adjacent to the view and don't contain it, which will be stored. If a rect doesn't match this criteria, it will be removed from the list of free rects and divided in 4 possible sub-rects - left, top, right, bottom - like this:

Sub rects

  • For each of that subrects, if none of the previously stored adjacent free rects and none of the rects in the list of free rects intersects with it, it will be added to this list as a valid free rect.

You can see this code in SpannedGridLayoutManager's subtract(Rect) method.

In version 3.0 however, to ensure no layout issues happened, the whole rects are pre-calculated and cached on every relayout of the whole LayoutManager. These are some elapsed time results that I got from testing:

Android emulator: 
    - 500 items: 10ms
    - 1000 items: 18ms
    - 10000 items: 35ms
    - 50000 items: 128ms
    
OnePlus 3T:
    - 500 items: 12ms
    - 1000 items: 20ms
    - 10000 items: 135ms
    - 50000 items: 530ms
    
OnePlus 6T:
    - 500 items: 5ms
    - 1000 items: 10ms
    - 10000 items: 55ms
    - 50000 items: 200ms

They aren't that bad, but they're also not great for huge amounts of items and could cause performance issues if you have infinite scrolling. I will try to improve this and calculate rects by batches, although this might not give exact results in the placement of the views.

License - MIT

MIT License

Copyright © 2017 Jorge Martín Espinosa

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.

spannedgridlayoutmanager's People

Contributors

gauthierchan avatar jmartinesp avatar sikeeoh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spannedgridlayoutmanager's Issues

NoSuchElementException on screen rotation

Don't know if it's related to your code or mine as I don't manage to reproduce it on the sample app but here is what happen to me.

I load items from network and put them into my adapter, no problem they appear, I rotate the screen it goes in landscape without problem and go back too.

But if in landscape I scroll down the list and then want to go back to portrait then it crash with :

java.util.NoSuchElementException: Collection contains no element matching the predicate.
                                                                        at com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.onSaveInstanceState(SpannedGridLayoutManager.kt:954)
                                                                        at android.support.v7.widget.RecyclerView.onSaveInstanceState(RecyclerView.java:1270)
                                                                        at android.view.View.dispatchSaveInstanceState(View.java)
                                                                        at android.view.ViewGroup.dispatchFreezeSelfOnly(ViewGroup.java)
                                                                        at android.support.v7.widget.RecyclerView.dispatchSaveInstanceState(RecyclerView.java:1297)
                                                                        at android.view.ViewGroup.dispatchSaveInstanceState(ViewGroup.java)
                                                                        at android.view.ViewGroup.dispatchSaveInstanceState(ViewGroup.java)
                                                                        at android.view.View.saveHierarchyState(View.java)
                                                                        at android.support.v4.app.FragmentManagerImpl.saveFragmentViewState(FragmentManager.java:2849)
                                                                        at android.support.v4.app.FragmentManagerImpl.saveFragmentBasicState(FragmentManager.java:2870)
                                                                        at android.support.v4.app.FragmentManagerImpl.saveAllState(FragmentManager.java:2923)
                                                                        at android.support.v4.app.FragmentController.saveAllState(FragmentController.java:125)
                                                                        at android.support.v4.app.FragmentActivity.onSaveInstanceState(FragmentActivity.java:528)
                                                                        at android.support.v7.app.AppCompatActivity.onSaveInstanceState(AppCompatActivity.java:509)
                                                                        at android.app.Activity.performSaveInstanceState(Activity.java)
                                                                        at android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java)
                                                                        at android.app.ActivityThread.callCallActivityOnSaveInstanceState(ActivityThread.java)
                                                                        at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java)
                                                                        at android.app.ActivityThread.-wrap19(ActivityThread.java)
                                                                        at android.app.ActivityThread$H.handleMessage(ActivityThread.java)
                                                                        at android.os.Handler.dispatchMessage(Handler.java)
                                                                        at android.os.Looper.loop(Looper.java)
                                                                        at android.app.ActivityThread.main(ActivityThread.java)
                                                                        at java.lang.reflect.Method.invoke(Native Method)
                                                                        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java)
                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java)

I made a quick video to show you the problem. Any idea ?

device-2017-10-31-175406.mp4.zip

Collapsing Toolbar doesn't expand when scrolling up

I have a problem in combination with the CollapsingToolbarLayout: When I scroll down, it correctly collapses the toolbar layout. BUT when I scroll back up, it doesn't expands it. This works when I use the default LayoutManagers.

See https://imgur.com/a/qiWMLnr

Here is my layout file:

<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:layout_behavior="com.levionsoftware.photos.workarounds.FixAppBarLayoutBehavior">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:collapsedTitleTextAppearance="@style/CollapsingToolbarLayoutStyleText"
            app:contentScrim="?attr/_colorPrimary"
            app:expandedTitleMarginEnd="16dp"
            app:expandedTitleMarginStart="16dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:clickable="true"
                android:focusable="true">

                <ImageView
                    android:id="@+id/main_subset_image_view"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:foreground="?android:attr/selectableItemBackground" />

                <TextView
                    android:id="@+id/date_text_view"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="8dp"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    style="@style/CTStyleTextBold" />

            </androidx.constraintlayout.widget.ConstraintLayout>

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

        </com.google.android.material.appbar.CollapsingToolbarLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <include layout="@layout/ads_view" />
        </LinearLayout>

    </com.google.android.material.appbar.AppBarLayout>

    <LinearLayout
        android:id="@+id/main_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabs"
            style="@style/Widget.MaterialComponents.TabLayout.Colored"
            app:tabIndicatorColor="?attr/_colorAccent"
            android:background="?attr/_colorPrimaryToolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            app:tabGravity="center"
            app:tabMode="fixed" />
        <!-- height: @dimen/pager_sliding_height -->

        <androidx.viewpager.widget.ViewPager
            android:id="@+id/view_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </LinearLayout>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/subset_fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="16dp"
        android:src="@drawable/ic_share_white_48dp"
        app:layout_anchor="@id/main_view"
        app:layout_anchorGravity="bottom|right|end" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Recyclerview items doesn't appear

In the Adapter onCreate/onBindViewHolder are not called!!

val recyclerLayoutManager = SpannedGridLayoutManager(
            orientation = SpannedGridLayoutManager.Orientation.VERTICAL,
            spans = 3
        )

//        recyclerLayoutManager.spanSizeLookup = GallerySpanSizeLookup()
        galleryRecycler.layoutManager = recyclerLayoutManager
        galleryRecycler.adapter = GalleryAdapter()

Kotlin null pointer exception

Hello! I really like this layout manager.

But there is a fatal exception that we found

Exception kotlin.KotlinNullPointerException:
com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.getDecoratedTop (SpannedGridLayoutManager.kt:637)
android.support.v7.widget.LinearSmoothScroller.calculateDyToMakeVisible (LinearSmoothScroller.java:308)
android.support.v7.widget.LinearSmoothScroller.onTargetFound (LinearSmoothScroller.java:113)
android.support.v7.widget.RecyclerView$SmoothScroller.onAnimation (RecyclerView.java:11242)
android.support.v7.widget.RecyclerView$SmoothScroller.access$600 (RecyclerView.java:11122)
android.support.v7.widget.RecyclerView$ViewFlinger.run (RecyclerView.java:4949)
android.view.Choreographer$CallbackRecord.run (Choreographer.java:858)
android.view.Choreographer.doCallbacks (Choreographer.java:670)
android.view.Choreographer.doFrame (Choreographer.java:603)
android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:844)
android.os.Handler.handleCallback (Handler.java:739)
android.os.Handler.dispatchMessage (Handler.java:95)
android.os.Looper.loop (Looper.java:234)
android.app.ActivityThread.main (ActivityThread.java:5526)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:726)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:616)

Recycler View Items not shown after back

Hello,

first of all, thanks for this great library =). I think i've found a bug. Every time when i open a new fragment and then go back to that fragment where i use the SpannedGridLayoutManager no items are shown until i scroll a little bit.

Here my code how i replace my fragments:

private void replaceFragment(@NonNull Fragment fragment) {
	try {
		FragmentManager fragmentManager = getSupportFragmentManager();
		fragmentManager.beginTransaction().replace(R.id.fragment_container, fragment, TAG_CURRENT_FRAGMENT).commit();
		mCurrentFragment = fragment;
	} catch (Exception e) {
		Log.e(TAG, e.getMessage());
	}
}

Am I doing something wrong?

Kind regards
Stefan

NoClassDefFoundError

I am unable to use the lib, I get the following error

java.lang.NoClassDefFoundError: Failed resolution of: Lkotlin/jvm/internal/Intrinsics;
        at com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.<init>(SpannedGridLayoutManager.kt)
        at cm.diginov.isitravel.activities.Home.HomeActivity.onCreate(HomeActivity.java:43)

This is how I use it

SpannedGridLayoutManager spannedGridLayoutManager = new SpannedGridLayoutManager(SpannedGridLayoutManager.Orientation.VERTICAL, 3);
spannedGridLayoutManager.setItemOrderIsStable(true);
rv_trips.setLayoutManager(spannedGridLayoutManager);
rv_trips.setAdapter(tripsAdapter);

Items not drawn

Hello there !

Thanks for the work on this library, it rocks ;)

By the way, I have an issue when scrolling up with this LayoutManager. Items are nicely recycled and drawn when scrolling down, by nothing is drawn on scolling up. An empty space with the good items heigh stays on top of the recyclerview, instead of the recycled views.

Has someone already experienced a similar issue ?

Edit: just to know, the same adapter code works fine with a classic GridLayoutManager.

Nested scrolling

Nested scrolling with my (and neither with your) code does not working at all. It just makes my screen unscrollable.

If i change the SpannedGridLayoutManager to a simple LinearLayoutManager, the NestedScrolling works fine, so the problem is in the LayoutManager.

Please fix it.
Thank you.

What if the item size is not int?

Hi,

Thanks for your wonderful code. I have a question about it, say I have a vertical list, the span count is 4. What if I want the item width is 1, and the height is 1.5?

Element size little too big ?

I have this layout:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto"
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">

    <data>

        <import type="android.view.View"/>

        <variable
            name="data"
            type="com.mylisabox.network.devices.models.Device"/>

        <variable
            name="builder"
            type="com.mylisabox.lisa.common.TemplateMobileViewBuilder"/>

        <variable
            name="populator"
            type="com.mylisabox.common.device.TemplateViewPopulator"/>

        <variable
            name="widgetHandler"
            type="com.mylisabox.common.device.WidgetHandler"/>
    </data>

    <android.support.v7.widget.CardView
        android:id="@+id/card_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        card_view:cardCornerRadius="4dp"
        card_view:cardElevation="@dimen/cardview_default_elevation">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <LinearLayout
                android:id="@+id/widgetHeader"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal">

                <TextView
                    android:id="@+id/widgetTitle"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:ellipsize="end"
                    android:lines="1"
                    android:maxLines="1"
                    android:padding="@dimen/space_normal"
                    android:text="@{data.name}"
                    android:textAppearance="?android:textAppearanceLarge"
                    android:textStyle="bold"
                    tools:text="My widget"/>

                <com.mylisabox.common.ui.ToggleImageButton
                    android:id="@+id/widgetFavorite"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="@null"
                    android:padding="@dimen/space_small"
                    android:checked="@{data.favorite}"
                    android:src="@drawable/ic_favorite_24dp"
                    android:tint="@drawable/ic_color_selector"/>
            </LinearLayout>

            <View
                style="@style/Divider"/>

            <LinearLayout
                android:id="@+id/widgetContent"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:orientation="vertical"
                bind:builder="@{builder}"
                bind:device="@{data}"
                bind:widgetHandler="@{widgetHandler}"
                bind:populator="@{populator}"/>
        </LinearLayout>
    </android.support.v7.widget.CardView>
</layout>

And it look like this in the preview:
capture d ecran 2017-11-05 a 20 12 04

But once on the emulator I get this:
device-2017-11-05-201349

Can't manage to find why my content is trunked as I don't have any fixed sizes. So maybe there a problem with the size of the element itself ?

Is there a way to force line height ?

Hello here.

I tried to force the line height in the case of a vertical spanned grid recycler view. On creating the ViewHolder, I set a new SpanLayoutParams with a computed height (here we try to have a specific number of line matching the recycler height).

But items are drawn with a height matching the width of a one-span-sized item.

Is there a way to achieve this with this SpannedGridLayoutManager ?

Thanks by advance.

Edit: just to know, the same methos of forcing items line height works fine with a classic GridLayoutManager.

Complex layout doesn't work

I'm trying to use this layout:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">

    <data>

        <import type="android.view.View"/>

        <variable
            name="data"
            type="com.mylisabox.network.devices.models.Device"/>

        <variable
            name="viewModel"
            type="com.mylisabox.lisa.device.viewmodels.DeviceViewModel"/>
    </data>

    <android.support.v7.widget.CardView
        android:id="@+id/card_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:layout_margin="@dimen/activity_vertical_margin"
        android:backgroundTint="@android:color/holo_green_light"
        card_view:cardCornerRadius="4dp"
        card_view:cardElevation="@dimen/cardview_default_elevation">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_green_dark"
            android:orientation="vertical">

            <LinearLayout
                android:id="@+id/widgetHeader"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_red_dark"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingRight="@dimen/space_normal">

                <TextView
                    android:id="@+id/widgetTitle"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:padding="@dimen/space_normal"
                    android:text="@{data.name}"
                    android:textAppearance="?android:textAppearanceLarge"
                    android:textStyle="bold"
                    tools:text="My widget"/>

                <com.mylisabox.common.ui.ToggleImageButton
                    android:id="@+id/widgetFavorite"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="@null"
                    android:checked="@{data.favorite}"
                    android:src="@drawable/ic_favorite_24dp"
                    android:tint="@drawable/ic_color_selector"/>
            </LinearLayout>

            <View
                style="@style/Divider"/>

            <LinearLayout
                android:id="@+id/widgetContent"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:orientation="vertical"/>
        </LinearLayout>
    </android.support.v7.widget.CardView>
</layout>

Witch look like:
capture d ecran 2017-10-01 a 20 21 37

But I have this:
device-2017-10-01-202331

If I force size of the card view's content I have this:
force-device-2017-10-01-202031

On my adapter I have like the sample:

val width = 4
val height = 3
holder.itemView.layoutParams = RecyclerView.LayoutParams(width, height)

And I think that's the problem, somewhere it need to refresh sub component of the itemView.
I also try more complex layout with the sample app but wasn't able to make it work.

Am I doing something wrong ? Is there any other example more complex in another repo ?

Automatic mode for span

For now we have to specify the number of span we want, is it possible to have a mode "auto" that calculate the number of span depending of the recyclerview width ? Like this if we rotate the screen it's recalculate to have more span.
Would be a nice addition

gone views after scroll

Hi,
take a look at this gif
ezgif-5-3c60896d75

compileSdkVersion 27
buildToolsVersion "27.0.3"
implementation 'com.android.support:recyclerview-v7:27.1.0'
implementation 'com.arasthel:spannedgridlayoutmanager:2.0.1'

Item order not respected when using SpanSize width = number of columns

I have the following code to collect my items to show in my RV:

    // Spanned
    public RecyclerView.LayoutManager getLayoutManager() {
        if (!mMainView || mDataHolder.getViewMode(mActivity).equals("grid")) { // We force the grid view in subset
            final Integer numColumns = UserPreferencesHandler.<Integer>readValue(mActivity, UserPreferencesHandler.PREF_KEY_NUM_COLUMNS);

            SpannedGridLayoutManager layoutManager = new SpannedGridLayoutManager(SpannedGridLayoutManager.Orientation.VERTICAL, numColumns);
            layoutManager.setSpanSizeLookup(new SpannedGridLayoutManager.SpanSizeLookup(position -> {
                try {
                    switch (RVAdapterTimeline.this.getItemViewType(position)) {
                        case RVAdapter.VIEW_TYPE_HEADER:
                            try {
                                return new SpanSize(numColumns, 1);
                            } catch (ClassCastException e) {
                                return new SpanSize(1, 1);
                            }
                        default:
                            LineItem lineitem = mItems.get(position);
                            if (lineitem != null && lineitem.mediaItem != null && lineitem.mediaItem.getRating() != null && lineitem.mediaItem.getRating() == 5) {
                                return new SpanSize(2, 2);
                            }

                            return new SpanSize(1, 1);
                    }
                } catch (IndexOutOfBoundsException e) {
                    MyApplication.toastSomething(mActivity, e); // Seen on firebase
                    return new SpanSize(1, 1);
                }
            }));

            return layoutManager;
        } else {
            return new LinearLayoutManager(mActivity, LinearLayoutManager.VERTICAL, false);
        }
    }

There are header items with a full column span:
return new SpanSize(numColumns, 1);
Other items are with span width 2 or 2. The number of columns is 5.

After collecting the items, they are used for the RV adapter. The problem is that the order in the rendered RV is not the same as the items. Result:
Pic

Could you please fix that? I can confirm 100% that the default GridLayoutManager is working with these items.

TypeCastException

I am getting the foloowing exception ...

kotlin.TypeCastException: View LayoutParams must be of type 'SpanLayoutParams'

Items are on bottom when small number

Looks ok when you have a lot of items, but when you have few of them it look like this:

device-2017-10-01-164151

To reproduce just take your sample app and replace 500 by 4 on getItemCount of the adapter

PS: tested on Android O on Nexus 5X

Glide preloader exception

@Arasthel I don't know whether it would be possible or not but because 'SpannedGridLayoutManager' has not been extended from 'LinearLayoutManager' a lot of code is breaking including the glide preloader component.

TypeCastException: Crashes the whole app, Untraceable

The library doesn't work for me, I don't know why, I get the following error

kotlin.TypeCastException: View LayoutParams must be of type 'com.arasthel.spannedgridlayoutmanager.SpanLayoutParams'
        at com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.measureChild(SpannedGridLayoutManager.kt:213)
        at com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.makeAndAddView(SpannedGridLayoutManager.kt:287)
        at com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.fillAfter(SpannedGridLayoutManager.kt:595)
        at com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.fillGap(SpannedGridLayoutManager.kt:554)
        at com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.onLayoutChildren(SpannedGridLayoutManager.kt:184)
...

TripsAdapter

class TripsAdapter extends RecyclerView.Adapter<TripsAdapter.MyViewHolder> {

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_trip, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        if (position == 0) holder.itemView.setLayoutParams(new SpanLayoutParams(new SpanSize(2, 2)));
    }

    @Override
    public int getItemCount() {
        return 9;
    }

    class MyViewHolder extends RecyclerView.ViewHolder {

        public MyViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
            itemView.setOnClickListener(this::onClick);
        }

        private void onClick(View view) {
            view.getContext().startActivity(new Intent(view.getContext(), LocalitiesActivity.class));
        }
    }
}

HomeActivity

public class HomeActivity extends BaseActivity {

    @BindView(R.id.rv_trips)
    RecyclerView rv_trips;
    private TripsAdapter tripsAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        ButterKnife.bind(this);

        disableFlags(true);
        enableWhiteStatusBar();
        Objects.requireNonNull(getSupportActionBar()).setTitle("Trips");
        tripsAdapter = new TripsAdapter();
       
        SpannedGridLayoutManager spannedGridLayoutManager = new SpannedGridLayoutManager(SpannedGridLayoutManager.Orientation.VERTICAL, 3);
        spannedGridLayoutManager.setItemOrderIsStable(true);
        rv_trips.setLayoutManager(spannedGridLayoutManager);
        rv_trips.setAdapter(tripsAdapter);
    }

}

Duplicate data

I dont know why i am adding data like 15 items i see that into my Recyclerview i have more.
I tried to use an other LayoutManager and i dont have this bug;

Also when i want to clear data and to add new the scroll position not reinitialize i have to scroll to the top in order to see the new data .

Pleaseeee helppppp.

(ui not responding) when adding new data

Good day sir,
first of all thanks for this amazing library, anyway i've encountered some issue when using this library, the ui keep hang(ui not responding) when i add set of data(i implemented pagination) and according to logcat "The application may be doing too much work on its main thread."

Thanks.

Empty space at the bottom

I just try version 3.0.2 when I scroll down to the bottom I found an empty space like this

ezgif-1-46ecebc152f2

I also add items at the start to show that empty space is still at the bottom

What I want to ask is, is this normal or I just did something wrong?

Setup layout

val layoutManager = SpannedGridLayoutManager(SpannedGridLayoutManager.Orientation.VERTICAL, 4)
        layoutManager.spanSizeLookup = SpannedGridLayoutManager.SpanSizeLookup { position ->
            when (position) {
                0 -> SpanSize(4, 2)
                1, 2 -> SpanSize(2, 2)
                else -> SpanSize(1, 1)
            }
        }
layoutManager.itemOrderIsStable = true

MainActivity's layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>

Item's layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:tools="http://schemas.android.com/tools">

    <android.support.constraint.ConstraintLayout
        android:id="@+id/item_layout_bg"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintDimensionRatio="H,1:1">

        <TextView
                android:id="@+id/item_layout_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="TEST"/>

    </android.support.constraint.ConstraintLayout>

</android.support.constraint.ConstraintLayout>

Unnecessary entry in Manifest

Please remove the whole application entry from the Manifest file, because it will cause problems like this one:

Error:Execution failed for task ':app:processDebugManifest'.

Manifest merger failed : Attribute application@allowBackup value=(false) from AndroidManifest.xml:34:9-36
is also present at [com.arasthel:spannedgridlayoutmanager:1.0.0] AndroidManifest.xml:12:9-35 value=(true).
Suggestion: add 'tools:replace="android:allowBackup"' to element at AndroidManifest.xml:32:5-175:19 to override.

A lib like yours has to do nothing with the Application but the app itself.

method to change initial 'span' value? ie: GridLayoutManager.setSpanCount()

I want to change the number of columns/spans when the user rotates the device - I was doing this using GridLayoutManager#setSpanCount() but don't see a similar method available for SpannedGridLayoutManager. Does one exist and I'm just missing it?

I ended up re-creating the layout manager after device rotates but at least it seems like this would be good to add if it makes sense..

Span width value more than available

For now if I put span 4 and on item span.width = 6, it crash, would be nice to have a dedicated error for this case. Also a mode to allow higher values to narrow down to max span automatically and be safe.

Unable to create inside of a fragment

When I copy the same code that from a this repo to a fragment and execute

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_feed, container, false);
        SpannedGridLayoutManager spannedGridLayoutManager = new 
        SpannedGridLayoutManager(SpannedGridLayoutManager.Orientation.VERTICAL, 3);

        RecyclerView recyclerView = view.findViewById(R.id.list_items);
        recyclerView.setLayoutManager(spannedGridLayoutManager);
        recyclerView.setAdapter(new ItemsAdapter());
        return view;
        

It shows up with nothing in it and when I move the exact same code to an activity it shows up perfectly

How to decrease the height of an item?

Thanks for this project :)

image

My left item has height = 2, and width = 1, and my GridLayout only contains 2 columns,
What I want is to make my height = 2 be smaller, is it possible to do this?

Items not drawn when scrolling up (extension of issues #19 and #15)

Hi :)

Another case of items not being drawn when scrolling up, I followed #19 and #15 and it solved it for most configurations but I found new cases where it doesn't work.

In the GIFs below I have a SpannedGridLayoutManager with a 4-width span. I mix 2x2, 1x1 and 4x1 blocks.

Working case

Just one 2x2 block and it works fine:

good

Non working case

Same as above but more 1x1 blocks after the 2x2 blocks

bug 4

Two "intertwined" 2x2 blocks

bug

Two adjacent 2x2 blocks

bug 2 2

Possible solution

In SpannedGridLayoutManager#fillBefore(), if I remove the canAddMoreViews(Direction.START, limit) condition as such:

/**
 * Fill gaps before the given position
 * @param position Item position in adapter
 * @param recycler Recycler
 * @param extraSpace Extra space to add to current [scroll] and use as limit for filling the gaps
 */
protected fun fillBefore(position: Int, recycler: RecyclerView.Recycler, extraSpace: Int) {
    var position = position
    val limit = scroll - extraSpace

    while (/* canAddMoreViews(Direction.END, limit) && */ position >= 0 ) {
        makeAndAddView(position, Direction.START, recycler)
        position--
    }
}

Then scrolling up doesn't seems to have drawing problems. But it has huge performance issues as I guess it draws all views that are above the current start 😄

So I found a hotfix to make the LayoutManager draw only necessary views:

/**
 * Checks if more views can be added between the current scroll and a limit
 */
protected fun canAddMoreViews(direction: Direction, limit: Int): Boolean {
    if (direction == Direction.START) {
        println(layoutStart.toString()+" | "+limit)
        return firstVisiblePosition > 0 && layoutStart > limit 
/***** add this -> *****/ || getChildAt(0) != null && getChildAt(0).top > -getChildAt(0).height
    } else {
        return limit > layoutEnd
    }
}

It makes the canAddMoreViews() more acceptable as of to when the LayoutManager should add a view at the top. Basically second-checking when it looks like there's not going to be any more views at the top 😄

The -getChildAt(0).height is totally arbitrary though, and might not work if views on the same column don't have the same height. And it is an ugly hotfix as it doesn't solve the original problem of why views weren't added.
Do you have any ideas on how to improve this ?

This also seems to make scrolling up working without having to add an ItemDecoration to the RecyclerView.


Here's an Adapter where you can reproduce the layouts I showed above. Change the onBindViewHolder() example call methods (They are in the same order as above) for the layout you want 😃

import android.graphics.Color
import android.support.v7.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout

class GridAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>(){

    init {
        setHasStableIds(true)
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

        showExample1(holder)
    }


    override fun getItemViewType(position: Int): Int {
        return position
    }

    override fun getItemCount(): Int {
        return 500
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GridItemViewHolder {
        val gridItemView = FrameLayout(parent.context)

        return GridItemViewHolder(gridItemView)
    }



    fun showExample1(holder: RecyclerView.ViewHolder){
        when(holder.adapterPosition) {

            0 -> set4x1Block(holder.itemView)
            in 1..4 -> set1x1Block(holder.itemView)
            5 -> set2x2Block(holder.itemView)
            else -> {

                if(holder.adapterPosition%5 == 0){
                    set4x1Block(holder.itemView)
                }else{
                    set1x1Block(holder.itemView)
                }
            }
        }
    }


    fun showExample2(holder: RecyclerView.ViewHolder){

        when(holder.adapterPosition) {

            0 -> set4x1Block(holder.itemView)
            in 1..4 -> set1x1Block(holder.itemView)
            5 -> set2x2Block(holder.itemView)
            in 6..10 -> set1x1Block(holder.itemView)
            else -> {

                if(holder.adapterPosition%5 == 0){
                    set4x1Block(holder.itemView)
                }else{
                    set1x1Block(holder.itemView)
                }
            }
        }
    }

    fun showExample3(holder: RecyclerView.ViewHolder){

        when(holder.adapterPosition) {

            0 -> set4x1Block(holder.itemView)
            in 1..4 -> set1x1Block(holder.itemView)
            5 -> set2x2Block(holder.itemView)
            in 6..7 -> set1x1Block(holder.itemView)
            8 -> set2x2Block(holder.itemView)
            else -> {

                if(holder.adapterPosition%5 == 0){
                    set4x1Block(holder.itemView)
                }else{
                    set1x1Block(holder.itemView)
                }
            }
        }
    }

    fun showExample4(holder: RecyclerView.ViewHolder){

        when(holder.adapterPosition) {

            0 -> set4x1Block(holder.itemView)
            in 1..4 -> set1x1Block(holder.itemView)
            in 5..6 -> set2x2Block(holder.itemView)
            else -> {

                if(holder.adapterPosition%5 == 0){
                    set4x1Block(holder.itemView)
                }else{
                    set1x1Block(holder.itemView)
                }
            }
        }
    }


    fun set1x1Block(itemView: View){
        val spanSize = SpanSize(1, 1)
        itemView.layoutParams = SpanLayoutParams(spanSize)
        itemView.setBackgroundColor(Color.RED)
    }
    fun set4x1Block(itemView: View){
        val spanSize = SpanSize(4, 1)
        itemView.layoutParams = SpanLayoutParams(spanSize)
        itemView.setBackgroundColor(Color.BLUE)
    }

    fun set2x2Block(itemView: View){
        val spanSize = SpanSize(2, 2)
        itemView.layoutParams = SpanLayoutParams(spanSize)
        itemView.setBackgroundColor(Color.GREEN)
    }




class GridItemViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView)

}

add the data of the adapter later, the position of the item is bottom

I have not already added the item status
2018-11-29 3 00 08

add item
2018-11-29 3 00 22

add item code

view1.setOnClickListener {
            adapter.clickedItems.clear()
            adapter.notifyDataSetChanged()
            for (i in 1..100) {
                adapter.clickedItems.add("hi")
            }
        adapter.notifyDataSetChanged()}
}

However, scrolling return the item to its normal position.
2018-11-29 2 59 53

kotlin.TypeCastException: View LayoutParams must be of type 'SpanLayoutParams'

When i call this code from lets say a OnClickListener i get this error , but when from outer no error it seems that something to do with context.

FATAL EXCEPTION: main
Process: inducesmile.com.androidrecyclerviewgridview, PID: 4632
kotlin.TypeCastException: View LayoutParams must be of type 'SpanLayoutParams'
at com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.measureChild(SpannedGridLayoutManager.kt:209)
at com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.makeAndAddView(SpannedGridLayoutManager.kt:283)
at com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.fillAfter(SpannedGridLayoutManager.kt:615)
at com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.fillGap(SpannedGridLayoutManager.kt:576)
at com.arasthel.spannedgridlayoutmanager.SpannedGridLayoutManager.onLayoutChildren(SpannedGridLayoutManager.kt:182)
at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3719)
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3436)
at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3988)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1055)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1983)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1740)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:996)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5600)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
at android.view.Choreographer.doCallbacks(Choreographer.java:574)
at android.view.Choreographer.doFrame(Choreographer.java:544)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(Native Method)

Can't scroll all the way down

Here's the skeleton of my XML for the main activity

<CoordinatorLayout>
    <AppBarLayout>
        <Toolbar/>
    </AppBarLayout>

    <FrameLayout/>

    <BottomNavigation/>
</CoordinatorLayout>

The FrameLayout holds a fragment and here's how it looks like:

<ConstraintLayout>

    <RecyclerView />

    <include layout="@layout/base_list_loading"/>

    <include  layout="@layout/base_list_empty"/>

</ConstraintLayout>

The problem is when using your SpannedGridLayoutManager, I'm not able to scroll all the way down. It Can just scroll for like 1/8th of the page.

I also have 3 Item Types:

  • Type 1 - SpanSize(2,3)
  • Type 2 - SpanSize(4,3)
  • Type 3 - SpanSize(4,6)

I can't use (1,1), (2,1), and (2,2) as your library changes the sizes and I need to have the layouts at the ratio for the images. It works when I use these though but then again, I need the sizes to be of that ratio.

Also, In your scrollBy function, the canScrollForward flag returns a false even though I'm haven't fully scrolled down the screen yet.

Would there be a way that we can define the size/ratio of 1 cell so as a SpanSize(1,1) can sort of look like a 2:3 square? Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.