2015年4月30日 星期四

[iOS] 如何讓object型態轉換為NSString並顯示在UI上?

今天又遇到讓我卡很久的難題了,一樣把它記錄起來以免日後忘記 .


首先.h檔跟storyboard(或xib檔)連結,h file長這樣:


#import <UIKit/UIKit.h>
#import "AppDelegate.h"
@interface Page1 : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *in_volt;
@property (weak, nonatomic) IBOutlet UILabel *out_volt;
@property (weak, nonatomic) IBOutlet UILabel *load;
@property (weak, nonatomic) IBOutlet UILabel *bat_level;
@property (weak, nonatomic) IBOutlet UILabel *bat_backup_time;
@property (weak, nonatomic) IBOutlet UILabel *ups_status;
@property (weak, nonatomic) IBOutlet UILabel *bat_stauts;
@property (weak, nonatomic) IBOutlet UILabel *power_condition;


@end


step1:

第一步是要讓UILable可以顯示資料

所以很簡單,在要觸發的地方寫下self.(UILabel名稱).text = (nsstring型態);

好了,很顯然第一個問題就是,我們現在是object型態,該怎樣轉NSString呢?

step2:

因為要顯示的是數字,所以先把它轉為int

object to int :

[(NSString *)object  integerValue]

這樣就變成int型態囉!

step3:

轉int還是不能顯示在UILabel上阿!

所以我們還需要再轉一次,這時候使用

[NSString stringWithFormat:@"%d", (int型態)]

但是呢!Xcode會跟你說warning,所以要改成

NSString stringWithFormat:@"%ld", (強制轉換型態成long,就是前面加個(long)這樣)]

完成就長這樣:

self.in_volt.text = [NSString stringWithFormat:@"%ld", (long)[(NSString *)你填入的object型態在這邊 integerValue]];

完成!

end

2015年4月29日 星期三

[iOS] Tab Bar分頁+UDP通訊

不知道怎樣寫分頁或是怎樣做UDP通訊嗎??

最近好不容易弄出了一點樣子,把它記錄起來,以免以後忘記.

首先分頁是使用tab bar元件-GG Tab Bar(https://github.com/Goles/GGTabBar)

,讓分頁切成四塊(同時建立四個.h+.m+.xib檔案)

可以看到有分了Page1,2,3,4分頁,以及對應的xib檔


首先,先在各個page.m裡面新增:


- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        self.tabBarItem = [[UITabBarItem alloc] initWithTitle:nil
                                                        image:[UIImage imageNamed:@"user_normal"]
                                                selectedImage:[UIImage imageNamed:@"user_pressed"]];
    }
    return self;
}

(image的部份就是要自己找圖或是下載source code,名稱自行調整,但需要對應到Images.xcassets裡面的檔案名稱)


step2: AppDelegate.h+m的部份

AppDelegate.h

#import <UIKit/UIKit.h>

#import "AsyncUdpSocket.h"
#define global ((AppDelegate *)[[UIApplication sharedApplication] delegate])

@interface AppDelegate : UIResponder <UIApplicationDelegate>
{
    AsyncUdpSocket *udpSocket;
    NSTimer *timer;
    long tag;
}
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic,retain) NSMutableArray *globalData;


@end


AppDelegate.m


#import "AppDelegate.h"
#import "GGTabBarController.h"
#import "Page1.h"
#import "Page2.h"
#import "Page3.h"
#import "Page4.h"


@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    
    NSLog(@"open");
    
    global.globalData = [[NSMutableArray alloc] initWithCapacity:300];
    
    udpSocket = [[AsyncUdpSocket alloc] initWithDelegate:self];
    NSError *error = nil;
    [udpSocket bindToPort:2601 error:&error]; //綁定一個port 讓發送的source port / 指定接收的port 都是這一個
    [udpSocket receiveWithTimeout:-1 tag:0];//Start listening for a UDP packet.
    timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(sender) userInfo:nil repeats:YES];
    
    GGTabBarController *tabBar = [[GGTabBarController alloc] init];
    
    Page1 *vc1 = [[Page1 alloc] init];
    Page2 *vc2 = [[Page2 alloc] init];
    Page3 *vc3 = [[Page3 alloc] init];
    Page4 *vc4 = [[Page4 alloc] init];
    
    tabBar.viewControllers = @[vc1, vc2, vc3, vc4];
    self.window.rootViewController = tabBar;
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

//Server 接收
-(BOOL) onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port
{
    //NSLog(@"in server");
    Byte *convert_to_byte = (Byte *)[data bytes];
    int length = data.length;
    //NSLog(@"length = %d",length);
    int receive[length];
    NSNumber *number;
    NSMutableArray *intArray = [[NSMutableArray alloc] init];
    
    for (int i = 0 ; i < [data length]; i++) {
        //printf("conver to byte = %d\n",convert_to_byte[i]);
        int combine = (int)convert_to_byte[i];
        //NSLog(@"%ld",combine);
        receive[i] = combine;
        number =[NSNumber numberWithInt:combine];
        [intArray addObject:number];
        NSLog(@"number =%@",number);
        //[global.globalData addObject:number];
        //NSLog(@"i=%@",global.globalData[i]);
        
        NSLog(@"%d",receive[i]);//詳細接收
    }
    [global.globalData setArray:intArray];
    [intArray removeAllObjects];
    
    for(int i = 0; i < global.globalData.count; i++){
        NSLog(@"i=%@",[global.globalData objectAtIndex:i]);
    }
    
    NSLog(@"global-data count= %lu",(unsigned long)global.globalData.count);
    
    //receive要解析,
    [udpSocket sendData:data toHost:host port:port withTimeout:-1 tag:0];
    [udpSocket receiveWithTimeout:-1 tag:0];
    return YES;
}


-(void) sender{
    //NSLog(@"timer");
    const unsigned char byte[] = {80,67,77,71,1,69,78,68};
    NSData *data = [NSData dataWithBytes:byte length:sizeof(byte)];
    [udpSocket sendData:data toHost:@"210.202.53.147" port:2601 withTimeout:-1 tag:tag];
}

@end

step3 : 建立udp通訊

這部份在前幾篇有講到附上網址:udp通訊part

(不過這部份已經有寫在上面了)



做完通訊,又有tab bar分頁功能以後,就是把分頁內容建立好囉!

教學到此結束,end.


[iOS] NSArray / NSMutableArray 與 全域變數

貓這次遇到的問題是,我需要一個全域變數而且是陣列,但是這個陣列需要可以變動式的.

因此NSArray就被排除在外面,講到NSArray就稍微講一下,基本上他是不能添加或減少任何元

素的,所以要改用mutable版本.


首先,第一步我們要在appdelegate.h這個檔案define一個全域的變數


#define global ((AppDelegate *)[[UIApplication sharedApplication] delegate])

global可以換成任何你想要的文字

然後還要宣告一個@property


@property (nonatomic, retain) NSMutableArray *globalData;

(globalData一樣可以換任何文字)


第二步,切換到appdelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    global.globalData = [[NSMutableArray alloc] initWithCapacity:300];
    
    return YES;

}

//初始化NSMutableArray

然後就可以使用囉!這邊可以用下面的for迴圈來看內容是什麼

    for(int i = 0; i < global.globalData.count; i++){
        NSLog(@"i=%@",[global.globalData objectAtIndex:i]);

    }

然後因為這是全域變數,所以不管在viewcontroller1,2,3,4....都可以看到這個變數

使用方法一樣是global.globalData來呼叫他,下一篇搭配UDP來操作,這次的教學完畢

end

2015年4月20日 星期一

[Android] Expandable ListView(可展開式的listview) 教學

最近需要寫有關Expandable Listview的東西,所以就把它紀錄一下!

至於該效果如何? 就是個可以展開/收起的一個listview(視覺效果不錯)
------------------------------------------------------------------------------------------

首先建立一個主要的xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/linear"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.daniel.exandableListview.AnimatedExpandableListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:choiceMode="singleChoice"
        android:listSelector="@drawable/selector_list_item"
         />
</RelativeLayout>


然後:

group_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingRight="10dp"
    android:paddingLeft="40dp">

    <TextView
        android:id="@+id/textTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingBottom="10dp"
        android:paddingTop="10dp"
        android:textAppearance="?android:attr/textAppearanceLarge"
         />
<!-- android:textColor="#FFFFFF" -->
</LinearLayout>


list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingRight="10dp"
    android:paddingLeft="50dp">

    <TextView
        android:id="@+id/textTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
         />
 <!-- android:textColor="#FFFFFF" -->

    <TextView
        android:id="@+id/textHint"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:textColor="#1874CD" />

</LinearLayout>


最後要增加一個Java檔:

AnimatedExpandableListView.java

package com.daniel.exandableListview; 


import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.Transformation;
import android.widget.AbsListView;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;

/**
 * This class defines an ExpandableListView which supports animations for
 * collapsing and expanding groups.
 */
public class AnimatedExpandableListView extends ExpandableListView {
    /*
     * A detailed explanation for how this class works:
     *
     * Animating the ExpandableListView was no easy task. The way that this
     * class does it is by exploiting how an ExpandableListView works.
     *
     * Normally when {@link ExpandableListView#collapseGroup(int)} or
     * {@link ExpandableListView#expandGroup(int)} is called, the view toggles
     * the flag for a group and calls notifyDataSetChanged to cause the ListView
     * to refresh all of it's view. This time however, depending on whether a
     * group is expanded or collapsed, certain childViews will either be ignored
     * or added to the list.
     *
     * Knowing this, we can come up with a way to animate our views. For
     * instance for group expansion, we tell the adapter to animate the
     * children of a certain group. We then expand the group which causes the
     * ExpandableListView to refresh all views on screen. The way that
     * ExpandableListView does this is by calling getView() in the adapter.
     * However since the adapter knows that we are animating a certain group,
     * instead of returning the real views for the children of the group being
     * animated, it will return a fake dummy view. This dummy view will then
     * draw the real child views within it's dispatchDraw function. The reason
     * we do this is so that we can animate all of it's children by simply
     * animating the dummy view. After we complete the animation, we tell the
     * adapter to stop animating the group and call notifyDataSetChanged. Now
     * the ExpandableListView is forced to refresh it's views again, except this
     * time, it will get the real views for the expanded group.
     *
     * So, to list it all out, when {@link #expandGroupWithAnimation(int)} is
     * called the following happens:
     *
     * 1. The ExpandableListView tells the adapter to animate a certain group.
     * 2. The ExpandableListView calls expandGroup.
     * 3. ExpandGroup calls notifyDataSetChanged.
     * 4. As an result, getChildView is called for expanding group.
     * 5. Since the adapter is in "animating mode", it will return a dummy view.
     * 6. This dummy view draws the actual children of the expanding group.
     * 7. This dummy view's height is animated from 0 to it's expanded height.
     * 8. Once the animation completes, the adapter is notified to stop
     *    animating the group and notifyDataSetChanged is called again.
     * 9. This forces the ExpandableListView to refresh all of it's views again.
     * 10.This time when getChildView is called, it will return the actual
     *    child views.
     *
     * For animating the collapse of a group is a bit more difficult since we
     * can't call collapseGroup from the start as it would just ignore the
     * child items, giving up no chance to do any sort of animation. Instead
     * what we have to do is play the animation first and call collapseGroup
     * after the animation is done.
     *
     * So, to list it all out, when {@link #collapseGroupWithAnimation(int)} is
     * called the following happens:
     *
     * 1. The ExpandableListView tells the adapter to animate a certain group.
     * 2. The ExpandableListView calls notifyDataSetChanged.
     * 3. As an result, getChildView is called for expanding group.
     * 4. Since the adapter is in "animating mode", it will return a dummy view.
     * 5. This dummy view draws the actual children of the expanding group.
     * 6. This dummy view's height is animated from it's current height to 0.
     * 7. Once the animation completes, the adapter is notified to stop
     *    animating the group and notifyDataSetChanged is called again.
     * 8. collapseGroup is finally called.
     * 9. This forces the ExpandableListView to refresh all of it's views again.
     * 10.This time when the ListView will not get any of the child views for
     *    the collapsed group.
     */

    @SuppressWarnings("unused")
private static final String TAG = AnimatedExpandableListAdapter.class.getSimpleName();

    /**
     * The duration of the expand/collapse animations
     */
    private static final int ANIMATION_DURATION = 300;

    private AnimatedExpandableListAdapter adapter;

    public AnimatedExpandableListView(Context context) {
        super(context);
    }

    public AnimatedExpandableListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public AnimatedExpandableListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * @see ExpandableListView#setAdapter(ExpandableListAdapter)
     */
    public void setAdapter(ExpandableListAdapter adapter) {
        super.setAdapter(adapter);

        // Make sure that the adapter extends AnimatedExpandableListAdapter
        if(adapter instanceof AnimatedExpandableListAdapter) {
            this.adapter = (AnimatedExpandableListAdapter) adapter;
            this.adapter.setParent(this);
        } else {
            throw new ClassCastException(adapter.toString() + " must implement AnimatedExpandableListAdapter");
        }
    }

    /**
     * Expands the given group with an animation.
     * @param groupPos The position of the group to expand
     * @return  Returns true if the group was expanded. False if the group was
     *          already expanded.
     */
    @SuppressLint("NewApi") 
    public boolean expandGroupWithAnimation(int groupPos) {
    boolean lastGroup = groupPos == adapter.getGroupCount() - 1;
    if (lastGroup && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
    return expandGroup(groupPos, true);
    }
   
        int groupFlatPos = getFlatListPosition(getPackedPositionForGroup(groupPos));
        if (groupFlatPos != -1) {
            int childIndex = groupFlatPos - getFirstVisiblePosition();
            if (childIndex < getChildCount()) {
                // Get the view for the group is it is on screen...
                View v = getChildAt(childIndex);
                if (v.getBottom() >= getBottom()) {
                    // If the user is not going to be able to see the animation
                    // we just expand the group without an animation.
                    // This resolves the case where getChildView will not be
                    // called if the children of the group is not on screen

                    // We need to notify the adapter that the group was expanded
                    // without it's knowledge
                    adapter.notifyGroupExpanded(groupPos);
                    return expandGroup(groupPos);
                }
            }
        }

        // Let the adapter know that we are starting the animation...
        adapter.startExpandAnimation(groupPos, 0);
        // Finally call expandGroup (note that expandGroup will call
        // notifyDataSetChanged so we don't need to)
        return expandGroup(groupPos);
    }

    /**
     * Collapses the given group with an animation.
     * @param groupPos The position of the group to collapse
     * @return  Returns true if the group was collapsed. False if the group was
     *          already collapsed.
     */
    public boolean collapseGroupWithAnimation(int groupPos) {
        int groupFlatPos = getFlatListPosition(getPackedPositionForGroup(groupPos));
        if (groupFlatPos != -1) {
            int childIndex = groupFlatPos - getFirstVisiblePosition();
            if (childIndex >= 0 && childIndex < getChildCount()) {
                // Get the view for the group is it is on screen...
                View v = getChildAt(childIndex);
                if (v.getBottom() >= getBottom()) {
                    // If the user is not going to be able to see the animation
                    // we just collapse the group without an animation.
                    // This resolves the case where getChildView will not be
                    // called if the children of the group is not on screen
                    return collapseGroup(groupPos);
                }
            } else {
                // If the group is offscreen, we can just collapse it without an
                // animation...
                return collapseGroup(groupPos);
            }
        }

        // Get the position of the firstChild visible from the top of the screen
        long packedPos = getExpandableListPosition(getFirstVisiblePosition());
        int firstChildPos = getPackedPositionChild(packedPos);
        int firstGroupPos = getPackedPositionGroup(packedPos);

        // If the first visible view on the screen is a child view AND it's a
        // child of the group we are trying to collapse, then set that
        // as the first child position of the group... see
        // {@link #startCollapseAnimation(int, int)} for why this is necessary
        firstChildPos = firstChildPos == -1 || firstGroupPos != groupPos ? 0 : firstChildPos;

        // Let the adapter know that we are going to start animating the
        // collapse animation.
        adapter.startCollapseAnimation(groupPos, firstChildPos);

        // Force the listview to refresh it's views
        adapter.notifyDataSetChanged();
        return isGroupExpanded(groupPos);
    }

    private int getAnimationDuration() {
        return ANIMATION_DURATION;
    }

    /**
     * Used for holding information regarding the group.
     */
    private static class GroupInfo {
        boolean animating = false;
        boolean expanding = false;
        int firstChildPosition;

        /**
         * This variable contains the last known height value of the dummy view.
         * We save this information so that if the user collapses a group
         * before it fully expands, the collapse animation will start from the
         * CURRENT height of the dummy view and not from the full expanded
         * height.
         */
        int dummyHeight = -1;
    }

    /**
     * A specialized adapter for use with the AnimatedExpandableListView. All
     * adapters used with AnimatedExpandableListView MUST extend this class.
     */
    public static abstract class AnimatedExpandableListAdapter extends BaseExpandableListAdapter {
        private SparseArray<GroupInfo> groupInfo = new SparseArray<GroupInfo>();
        private AnimatedExpandableListView parent;

        private static final int STATE_IDLE = 0;
        private static final int STATE_EXPANDING = 1;
        private static final int STATE_COLLAPSING = 2;

        private void setParent(AnimatedExpandableListView parent) {
            this.parent = parent;
        }

        public int getRealChildType(int groupPosition, int childPosition) {
            return 0;
        }

        public int getRealChildTypeCount() {
            return 1;
        }

        public abstract View getRealChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent);
        public abstract int getRealChildrenCount(int groupPosition);

        private GroupInfo getGroupInfo(int groupPosition) {
            GroupInfo info = groupInfo.get(groupPosition);
            if (info == null) {
                info = new GroupInfo();
                groupInfo.put(groupPosition, info);
            }
            return info;
        }

        public void notifyGroupExpanded(int groupPosition) {
            GroupInfo info = getGroupInfo(groupPosition);
            info.dummyHeight = -1;
        }

        private void startExpandAnimation(int groupPosition, int firstChildPosition) {
            GroupInfo info = getGroupInfo(groupPosition);
            info.animating = true;
            info.firstChildPosition = firstChildPosition;
            info.expanding = true;
        }

        private void startCollapseAnimation(int groupPosition, int firstChildPosition) {
            GroupInfo info = getGroupInfo(groupPosition);
            info.animating = true;
            info.firstChildPosition = firstChildPosition;
            info.expanding = false;
        }

        private void stopAnimation(int groupPosition) {
            GroupInfo info = getGroupInfo(groupPosition);
            info.animating = false;
        }

        /**
         * Override {@link #getRealChildType(int, int)} instead.
         */
        @Override
        public final int getChildType(int groupPosition, int childPosition) {
            GroupInfo info = getGroupInfo(groupPosition);
            if (info.animating) {
                // If we are animating this group, then all of it's children
                // are going to be dummy views which we will say is type 0.
                return 0;
            } else {
                // If we are not animating this group, then we will add 1 to
                // the type it has so that no type id conflicts will occur
                // unless getRealChildType() returns MAX_INT
                return getRealChildType(groupPosition, childPosition) + 1;
            }
        }

        /**
         * Override {@link #getRealChildTypeCount()} instead.
         */
        @Override
        public final int getChildTypeCount() {
            // Return 1 more than the childTypeCount to account for DummyView
            return getRealChildTypeCount() + 1;
        }
        
        protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
            return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                                                ViewGroup.LayoutParams.WRAP_CONTENT, 0);
        }

        /**
         * Override {@link #getChildView(int, int, boolean, View, ViewGroup)} instead.
         */
        @Override
        public final View getChildView(final int groupPosition, int childPosition, boolean isLastChild, View convertView, final ViewGroup parent) {
            final GroupInfo info = getGroupInfo(groupPosition);

            if (info.animating) {
                // If this group is animating, return the a DummyView...
                if (convertView instanceof DummyView == false) {
                    convertView = new DummyView(parent.getContext());
                    convertView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, 0));
                }

                if (childPosition < info.firstChildPosition) {
                    // The reason why we do this is to support the collapse
                    // this group when the group view is not visible but the
                    // children of this group are. When notifyDataSetChanged
                    // is called, the ExpandableListView tries to keep the
                    // list position the same by saving the first visible item
                    // and jumping back to that item after the views have been
                    // refreshed. Now the problem is, if a group has 2 items
                    // and the first visible item is the 2nd child of the group
                    // and this group is collapsed, then the dummy view will be
                    // used for the group. But now the group only has 1 item
                    // which is the dummy view, thus when the ListView is trying
                    // to restore the scroll position, it will try to jump to
                    // the second item of the group. But this group no longer
                    // has a second item, so it is forced to jump to the next
                    // group. This will cause a very ugly visual glitch. So
                    // the way that we counteract this is by creating as many
                    // dummy views as we need to maintain the scroll position
                    // of the ListView after notifyDataSetChanged has been
                    // called.
                    convertView.getLayoutParams().height = 0;
                    return convertView;
                }

                final ExpandableListView listView = (ExpandableListView) parent;

                final DummyView dummyView = (DummyView) convertView;

                // Clear the views that the dummy view draws.
                dummyView.clearViews();

                // Set the style of the divider
                dummyView.setDivider(listView.getDivider(), parent.getMeasuredWidth(), listView.getDividerHeight());

                // Make measure specs to measure child views
                final int measureSpecW = MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.EXACTLY);
                final int measureSpecH = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

                int totalHeight = 0;
                int clipHeight = parent.getHeight();

                final int len = getRealChildrenCount(groupPosition);
                for (int i = info.firstChildPosition; i < len; i++) {
                    View childView = getRealChildView(groupPosition, i, (i == len - 1), null, parent);
                    
                    LayoutParams p = (LayoutParams) childView.getLayoutParams();
                    if (p == null) {
                        p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
                        childView.setLayoutParams(p);
                    }
                    
                    int lpHeight = p.height;
                    
                    int childHeightSpec;
                    if (lpHeight > 0) {
                        childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
                    } else {
                        childHeightSpec = measureSpecH;
                    }
                    
                    childView.measure(measureSpecW, childHeightSpec);
                    totalHeight += childView.getMeasuredHeight();
                    
                    if (totalHeight < clipHeight) {
                        // we only need to draw enough views to fool the user...
                        dummyView.addFakeView(childView);
                    } else {
                        dummyView.addFakeView(childView);
                        
                        // if this group has too many views, we don't want to
                        // calculate the height of everything... just do a light
                        // approximation and break
                        int averageHeight = totalHeight / (i + 1);
                        totalHeight += (len - i - 1) * averageHeight;
                        break;
                    }
                }

                Object o;
                int state = (o = dummyView.getTag()) == null ? STATE_IDLE : (Integer) o;

                if (info.expanding && state != STATE_EXPANDING) {
                    ExpandAnimation ani = new ExpandAnimation(dummyView, 0, totalHeight, info);
                    ani.setDuration(this.parent.getAnimationDuration());
                    ani.setAnimationListener(new AnimationListener() {

                        @Override
                        public void onAnimationEnd(Animation animation) {
                            stopAnimation(groupPosition);
                            notifyDataSetChanged();
                            dummyView.setTag(STATE_IDLE);
                        }

                        @Override
                        public void onAnimationRepeat(Animation animation) {}

                        @Override
                        public void onAnimationStart(Animation animation) {}

                    });
                    dummyView.startAnimation(ani);
                    dummyView.setTag(STATE_EXPANDING);
                } else if (!info.expanding && state != STATE_COLLAPSING) {
                    if (info.dummyHeight == -1) {
                        info.dummyHeight = totalHeight;
                    }

                    ExpandAnimation ani = new ExpandAnimation(dummyView, info.dummyHeight, 0, info);
                    ani.setDuration(this.parent.getAnimationDuration());
                    ani.setAnimationListener(new AnimationListener() {

                        @Override
                        public void onAnimationEnd(Animation animation) {
                            stopAnimation(groupPosition);
                            listView.collapseGroup(groupPosition);
                            notifyDataSetChanged();
                            info.dummyHeight = -1;
                            dummyView.setTag(STATE_IDLE);
                        }

                        @Override
                        public void onAnimationRepeat(Animation animation) {}

                        @Override
                        public void onAnimationStart(Animation animation) {}

                    });
                    dummyView.startAnimation(ani);
                    dummyView.setTag(STATE_COLLAPSING);
                }

                return convertView;
            } else {
                return getRealChildView(groupPosition, childPosition, isLastChild, convertView, parent);
            }
        }

        @Override
        public final int getChildrenCount(int groupPosition) {
            GroupInfo info = getGroupInfo(groupPosition);
            if (info.animating) {
                return info.firstChildPosition + 1;
            } else {
                return getRealChildrenCount(groupPosition);
            }
        }

    }

    private static class DummyView extends View {
        private List<View> views = new ArrayList<View>();
        private Drawable divider;
        private int dividerWidth;
        private int dividerHeight;

        public DummyView(Context context) {
            super(context);
        }

        public void setDivider(Drawable divider, int dividerWidth, int dividerHeight) {
       if(divider != null) {
       this.divider = divider;
       this.dividerWidth = dividerWidth;
       this.dividerHeight = dividerHeight;

       divider.setBounds(0, 0, dividerWidth, dividerHeight);
       }
        }

        /**
         * Add a view for the DummyView to draw.
         * @param childView View to draw
         */
        public void addFakeView(View childView) {
            childView.layout(0, 0, getWidth(), childView.getMeasuredHeight());
            views.add(childView);
        }
        
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            final int len = views.size();
            for(int i = 0; i < len; i++) {
                View v = views.get(i);
                v.layout(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
            }
        }

        public void clearViews() {
            views.clear();
        }

        @Override
        public void dispatchDraw(Canvas canvas) {
            canvas.save();
            if(divider != null) {
                divider.setBounds(0, 0, dividerWidth, dividerHeight);
            }
            
            final int len = views.size();
            for(int i = 0; i < len; i++) {
                View v = views.get(i);
                
                canvas.save();
                canvas.clipRect(0, 0, getWidth(), v.getMeasuredHeight());
                v.draw(canvas);
                canvas.restore();
                
                if(divider != null) {
                    divider.draw(canvas);
                    canvas.translate(0, dividerHeight);
                }
                
                canvas.translate(0, v.getMeasuredHeight());
            }
            
            canvas.restore();
        }
    }

    private static class ExpandAnimation extends Animation {
        private int baseHeight;
        private int delta;
        private View view;
        private GroupInfo groupInfo;

        private ExpandAnimation(View v, int startHeight, int endHeight, GroupInfo info) {
            baseHeight = startHeight;
            delta = endHeight - startHeight;
            view = v;
            groupInfo = info;

            view.getLayoutParams().height = startHeight;
            view.requestLayout();
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            if (interpolatedTime < 1.0f) {
                int val = baseHeight + (int) (delta * interpolatedTime);
                view.getLayoutParams().height = val;
                groupInfo.dummyHeight = val;
                view.requestLayout();
            } else {
                int val = baseHeight + delta;
                view.getLayoutParams().height = val;
                groupInfo.dummyHeight = val;
                view.requestLayout();
            }
        }
    }
}

---------------------------------------------------------------------------------------------------------------------

準備工作都完成以後,我們就可以來使用這個expandable listview,但是很顯然還不知道該如何使

用,所以現在就來教學囉!

這邊是用一個分頁(fragment)來做個listview,所以寫在oncreateview()裡面,

首先是標題:

標題使用String[] Title_big 來建立標題集合

中標與小標(或註解)則是使用ChildItem 來建立(這裡是用switch case來判別每一個中標/註解放

甚麼東西)

有了大標題/中標題+註解之後,接下來的問題就是如何點選以後觸發事件??

 listView.setOnChildClickListener(new OnChildClickListener(){
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
int index = parent.getFlatListPosition(ExpandableListView.getPackedPositionForChild(groupPosition, childPosition));
    parent.setItemChecked(index, true);
switch (groupPosition) {

這一段就是讓你的中標題能觸發事件,我們用switch...case來取得groupPosition

然後再去裡面細分childPosition,最後才在裡面寫觸發事件!


package com.Daniel_pcm.android;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupClickListener;
import android.widget.TextView;

import com.daniel.exandableListview.AnimatedExpandableListView;

public class Setting extends Fragment{

private AnimatedExpandableListView listView;
    private ExampleAdapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View v = LayoutInflater.from(getActivity()).inflate(R.layout.tab3_setting,null);
      List<GroupItem> items = new ArrayList<GroupItem>();
       
      String[] Title_big = new String[]{MainActivity.Language_content[65],MainActivity.Language_content[63],MainActivity.Language_content[77]};
      //標題
       // Populate our list with groups and it's children
       for(int i = 0; i < Title_big.length; i++) { //總數
       
       GroupItem item = new GroupItem();    
       item.title = Title_big[i]; //大標題名稱
   
       if (i == 0) {
for (int j = 0; j < 2; j++) {
ChildItem child = new ChildItem();
if(j == 0)
{
child.title = ""; // 中標題名稱 + 編號
child.hint = "";//這邊可以改為直接顯示在這
}
if(j == 1)
{
child.title = ""; // 中標題名稱 + 編號
child.hint = "";
}
item.items.add(child);
}
}        
       
if (i == 1) {
// ChildItem child = new ChildItem();
// child.title = "多國語言";
// child.hint = "選擇其他語言";
// item.items.add(child);
for (int j = 0; j < 7; j++) {
ChildItem child = new ChildItem();
if(j == 0)
{
child.title = "中文"; // 中標題名稱 + 編號
child.hint = "Chinese";
}
if(j == 1)
{
child.title = "English"; // 中標題名稱 + 編號
child.hint = "English";
}
if(j == 2)
{
child.title = "русский язык"; // 中標題名稱 + 編號
child.hint = "Russian";
}
if(j == 3)
{
child.title = "اللغة العربية‎"; // 中標題名稱 + 編號
child.hint = "Arabian";
}
if(j == 4)
{
child.title = "español"; // 中標題名稱 + 編號
child.hint = "Spainish";
}
if(j == 5)
{
child.title = "Italiano"; // 中標題名稱 + 編號
child.hint = "Italish";
}
if(j == 6)
{
child.title = "Deutsch"; // 中標題名稱 + 編號
child.hint = "Deutsch";
}
item.items.add(child);
}
}

if (i == 2) {
for (int j = 0; j < 4; j++) {
ChildItem child = new ChildItem();
if(j == 0)
{
child.title = ""; // 中標題名稱 + 編號
child.hint = "";
}
if(j == 1)
{
child.title = ""; // 中標題名稱 + 編號
child.hint = "";
}
if(j == 2)
{
child.title = ""; // 中標題名稱 + 編號
child.hint = "";
}
if(j == 3)
{
child.title = ""; // 中標題名稱 + 編號
child.hint = "";
}
item.items.add(child);
}
}
           items.add(item);
       }
       
       Context mContext = getActivity();
       adapter = new ExampleAdapter(mContext);
       adapter.setData(items);
       
       listView = (AnimatedExpandableListView) v.findViewById(R.id.listView);
       listView.setAdapter(adapter);
       listView.setOnChildClickListener(new OnChildClickListener(){
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
int index = parent.getFlatListPosition(ExpandableListView.getPackedPositionForChild(groupPosition, childPosition));
   parent.setItemChecked(index, true);
switch (groupPosition) {
case 0: //第一組(連線)
switch (childPosition) {
case 0://第一個選項-目前IP
Log.d("one","connect child!");
MainActivity.handler_main.sendMessage(MainActivity.handler_main.obtainMessage(6));
break;
case 1://第二個選項-連線
//popup_ip
MainActivity.handler_main.sendMessage(MainActivity.handler_main.obtainMessage(3));
break;
}
break;
case 1://第二組(語言)
System.out.print("Language change in!");
int language_serial = 0;
switch (childPosition) {
case 0://第一選項(選語言)
language_serial = 0;
break;
case 1:
language_serial = 1;
break;
case 2:
language_serial = 2;
break;
case 3:
language_serial = 3;
break;
case 4:
language_serial = 4;
break;
case 5:
language_serial = 5;
break;
case 6:
language_serial = 6;
break;
}
MainActivity.handler_main.sendMessage(MainActivity.handler_main.obtainMessage(4,language_serial));
System.out.print("Language change success!");
break;
case 2://第三組(控制)
switch (childPosition) {
case 0://第一選項(outlet control)
MainActivity.handler_main.sendMessage(MainActivity.handler_main.obtainMessage(5));
break;
case 1://第二選項(UPS quick test)
if (Static_Var.PRO_now == true) {
// 如果現在是PRO
Static_Var.PRO_Self_test = true;
Log.d("PRO Self test flag","on");
}

if (Static_Var.SNMP_now == true) {
// 如果現在是SNMP
Static_Var.SNMP_Self_test = true;
Log.d("SNMP Self test flag","on");
}
break;
case 2://第三選項(UPS Deep test)
break;
case 3://第四選項(取消test)
break;
}
break;
}
return false;
}
       });
       
       // In order to show animations, we need to use a custom click handler
       // for our ExpandableListView.
       listView.setOnGroupClickListener(new OnGroupClickListener() {

           @Override
           public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
               // We call collapseGroupWithAnimation(int) and
               // expandGroupWithAnimation(int) to animate group 
               // expansion/collapse.
               if (listView.isGroupExpanded(groupPosition)) {
                   listView.collapseGroupWithAnimation(groupPosition);
                   //Log.d("Position",Integer.toString(groupPosition));//GroupPosition 是最大標題
               } else {
                   listView.expandGroupWithAnimation(groupPosition);
               }
               return true;
           }
           
       });
return v;
}
    private static class GroupItem {
        String title;
        List<ChildItem> items = new ArrayList<ChildItem>();
    }
    
    private static class ChildItem {
        String title;
        String hint;
    }
    
    private static class ChildHolder {
        TextView title;
        TextView hint;
    }
    
    private static class GroupHolder {
        TextView title;
    }
    /**
     * Adapter for our list of {@link GroupItem}s.
     */
    private class ExampleAdapter extends com.daniel.exandableListview.AnimatedExpandableListView.AnimatedExpandableListAdapter {
        private LayoutInflater inflater;
        
        private List<GroupItem> items;
        
        public ExampleAdapter(Context context) {
             inflater = LayoutInflater.from(context);
        }

        public void setData(List<GroupItem> items) {
            this.items = items;
        }

        @Override
        public ChildItem getChild(int groupPosition, int childPosition) {
            return items.get(groupPosition).items.get(childPosition);
        }

        @Override
        public long getChildId(int groupPosition, int childPosition) {
            return childPosition;
        }

        @Override
        public View getRealChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
            ChildHolder holder;
            ChildItem item = getChild(groupPosition, childPosition);
            if (convertView == null) {
                holder = new ChildHolder();
                convertView = inflater.inflate(R.layout.list_item, parent, false);
                holder.title = (TextView) convertView.findViewById(R.id.textTitle);
                holder.hint = (TextView) convertView.findViewById(R.id.textHint);
                convertView.setTag(holder);
            } else {
                holder = (ChildHolder) convertView.getTag();
            }
            
            holder.title.setText(item.title);
            holder.hint.setText(item.hint);
            
            return convertView;
        }

        @Override
        public int getRealChildrenCount(int groupPosition) {
            return items.get(groupPosition).items.size();
        }

        @Override
        public GroupItem getGroup(int groupPosition) {
            return items.get(groupPosition);
        }

        @Override
        public int getGroupCount() {
            return items.size();
        }

        @Override
        public long getGroupId(int groupPosition) {
            return groupPosition;
        }

        @Override
        public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
            GroupHolder holder;
            GroupItem item = getGroup(groupPosition);
            if (convertView == null) {
                holder = new GroupHolder();
                convertView = inflater.inflate(R.layout.group_item, parent, false);
                holder.title = (TextView) convertView.findViewById(R.id.textTitle);
                convertView.setTag(holder);
            } else {
                holder = (GroupHolder) convertView.getTag();
            }
            
            holder.title.setText(item.title);          
            
            return convertView;
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }

        @Override
        public boolean isChildSelectable(int arg0, int arg1) {
            return true;
        }
        
    }
}

整個Expandable listview教學就到這裡!

基本上,稍作修改應該就可以變成自己的東西囉!

END